diff --git a/comfy/__init__.py b/comfy/__init__.py index 6026eb0..b0ef555 100644 --- a/comfy/__init__.py +++ b/comfy/__init__.py @@ -1,3 +1,5 @@ +import os +import shutil from server import PromptServer from aiohttp import web, WSMsgType import json @@ -61,6 +63,7 @@ class PhotoshopInstance: self.wsCallsManager = WSCallsManager(ws) self.destroyed = False self.layers = [] + self.push_data = {} self.get_img_state_id = None self.last_get_img_id = None self.send_img_state_id = None @@ -87,7 +90,9 @@ class PhotoshopInstance: if msg.type == WSMsgType.TEXT: payload = json.loads(msg.data) - + if 'push_data' in payload: + push_data = payload['push_data'] + self.push_data.update(push_data) if 'call_id' in payload: call_id = payload['call_id'] if 'error' in payload: @@ -107,6 +112,9 @@ class PhotoshopInstance: finally: print('Photoshop Disconnected') PhotoshopInstance.instance = None + + def get_push_state_id(self): + return self.push_data.get('history_state_id', None) async def get_layers(self): result = await self.wsCallsManager.call('get_layers', {}) @@ -123,8 +131,11 @@ class PhotoshopInstance: self.last_get_img_id = id return id - def is_get_image_changed(self): - current_id = _invoke_async(self.get_active_history_state_id()) + async def is_get_image_changed(self): + push_state_id = self.get_push_state_id() + if push_state_id is not None: + return self.get_img_state_id != push_state_id + current_id = await self.get_active_history_state_id() return self.get_img_state_id != current_id async def send_images(self, image_ids, layer_name=""): @@ -139,7 +150,7 @@ class PhotoshopInstance: async def get_active_history_state_id(self): result = await self.wsCallsManager.call('get_active_history_state_id', {}) - id = result['history_state_id'] + id = result.get('history_state_id', None) return id async def destroy(self): @@ -258,7 +269,7 @@ class GetImageFromPhotoshopLayerNode: if (PhotoshopInstance.instance is None): return np.random.rand() else: - return PhotoshopInstance.instance.is_get_image_changed() + return _invoke_async(PhotoshopInstance.instance.get_active_history_state_id()) @classmethod def INPUT_TYPES(cls): @@ -376,15 +387,16 @@ class SendImageToPhotoshopSetLayerNode: threading.Thread(target=lambda: asyncio.run(PhotoshopInstance.instance.send_images(image_ids=ret, layer_name=layer_name))).start() return (None,) -loop = None def _invoke_async(call): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - task = loop.create_task(call) - loop.run_until_complete(task) - result = task.result() - loop.close() - return result + return asyncio.run(call) + + +@PromptServer.instance.routes.get("/sd-ppp/checkchanges") +async def fetch_customnode_mappings(request): + is_changed = False + if (PhotoshopInstance.instance is not None): + is_changed = await PhotoshopInstance.instance.is_get_image_changed() + return web.json_response({'is_changed': is_changed}, content_type='application/json') NODE_CLASS_MAPPINGS = { 'Get Image From Photoshop Layer': GetImageFromPhotoshopLayerNode, diff --git a/comfy/plugins/sd-ppp.js b/comfy/plugins/sd-ppp.js new file mode 100644 index 0000000..f0af515 --- /dev/null +++ b/comfy/plugins/sd-ppp.js @@ -0,0 +1,28 @@ +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js" + +console.log("[sd-ppp]", "Loading js extension"); +app.registerExtension({ + name: "Comfy.SD-PPP", + init() { + }, + async setup() { + setInterval(checkChanges, 1000); + }, +}); + +const SDPPPNodes = [ + 'Get Image From Photoshop Layer', + 'Send Images To Photoshop', + 'Send Images To Photoshop Set Layer', +] +async function checkChanges() { + const currentState = app.graph.serialize(); + const mode0NodeTypes = currentState.nodes.filter(node => node.mode == 0).map(node => node.type); + const containsSDPPPNodes = mode0NodeTypes.some(nodeType => SDPPPNodes.includes(nodeType)); + if (!containsSDPPPNodes) return; + const res = await api.fetchApi(`/sd-ppp/checkchanges`); + const json = await res.json() + if (!json.is_changed) return; + api.dispatchEvent(new CustomEvent("graphChanged")); +} \ No newline at end of file diff --git a/photoshop/dist/index.js b/photoshop/dist/index.js index 6cdbbf5..2e0da82 100644 --- a/photoshop/dist/index.js +++ b/photoshop/dist/index.js @@ -88,8 +88,10 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ }); /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "./node_modules/react/index.js"); /* harmony import */ var _system_ComfyConnection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../system/ComfyConnection */ "./src/system/ComfyConnection.js"); -/* harmony import */ var uxp__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! uxp */ "uxp"); -/* harmony import */ var uxp__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(uxp__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _system_HistoryChecker__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../system/HistoryChecker */ "./src/system/HistoryChecker.js"); +/* harmony import */ var uxp__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! uxp */ "uxp"); +/* harmony import */ var uxp__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(uxp__WEBPACK_IMPORTED_MODULE_3__); + @@ -106,8 +108,18 @@ class Main extends react__WEBPACK_IMPORTED_MODULE_0__.Component { isConnected: instance?.isConnected, comfyURL: instance ? instance.comfyURL : '' }); + if (instance?.isConnected) { + const hc = _system_HistoryChecker__WEBPACK_IMPORTED_MODULE_2__["default"].createInstance(); + hc.setChangeCallback(historyId => { + instance.pushData({ + history_state_id: historyId + }); + }); + } else { + _system_HistoryChecker__WEBPACK_IMPORTED_MODULE_2__["default"].instance?.destroy(); + } }); - uxp__WEBPACK_IMPORTED_MODULE_2__.storage.secureStorage.getItem('comfyURL').then(value => { + uxp__WEBPACK_IMPORTED_MODULE_3__.storage.secureStorage.getItem('comfyURL').then(value => { if (!value) return; this.setState({ comfyURL: Buffer.from(value).toString() @@ -284,6 +296,19 @@ class ComfyConnection { this.comfyURL = comfyURL.replace(/\/*$/, ''); this.connect(); } + pushData(data) { + if (!this.socket || this.socket.readyState != WebSocket.OPEN) { + console.error('Connection not open'); + return; + } + try { + this.socket.send(JSON.stringify({ + push_data: data + })); + } catch (e) { + console.error(e); + } + } async onMessage(event) { console.log("Message from comfy ", event.data); try { @@ -415,13 +440,15 @@ class ComfyConnection { }); } else if (payload.action == 'get_active_history_state_id') { try { - const historyStates = photoshop__WEBPACK_IMPORTED_MODULE_0__.app.activeDocument.historyStates; - const historyState = historyStates[historyStates.length - 1]; + let result = {}; + const historyStates = photoshop__WEBPACK_IMPORTED_MODULE_0__.app.activeDocument?.historyStates; + if (historyStates && historyStates.length > 0) { + const historyState = historyStates[historyStates.length - 1]; + result.history_state_id = historyState.id; + } this.socket.send(JSON.stringify({ call_id: payload.call_id, - result: { - history_state_id: historyState.id - } + result: result })); } catch (e) { this.socket.send(JSON.stringify({ @@ -473,6 +500,62 @@ class ComfyConnection { /***/ }), +/***/ "./src/system/HistoryChecker.js": +/*!**************************************!*\ + !*** ./src/system/HistoryChecker.js ***! + \**************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var photoshop__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! photoshop */ "photoshop"); +/* harmony import */ var photoshop__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(photoshop__WEBPACK_IMPORTED_MODULE_0__); + +class HistoryChecker { + static instance = null; + static createInstance() { + if (HistoryChecker.instance) { + return HistoryChecker.instance; + } + return new HistoryChecker(); + } + constructor() { + HistoryChecker.instance = this; + this.lastCheckId = -1; + this.changeCallback = null; + // this.timer = setInterval(() => { + // this.checkHistoryState(); + // }, 1000) + photoshop__WEBPACK_IMPORTED_MODULE_0__.action.addNotificationListener("historyStateChanged", () => { + this.checkHistoryState(); + }); + } + destroy() { + clearInterval(this.timer); + this.timer = null; + this.changeCallback = null; + HistoryChecker.instance = null; + } + setChangeCallback(callback) { + this.changeCallback = callback; + } + checkHistoryState() { + const historyStates = photoshop__WEBPACK_IMPORTED_MODULE_0__.app.activeDocument?.historyStates; + if (!historyStates || historyStates.length == 0) return; + const historyState = historyStates[historyStates.length - 1]; + const historyId = historyState.id; + if (this.lastCheckId == historyId) return; + this.lastCheckId = historyId; + if (this.changeCallback) this.changeCallback(historyId); + } +} +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (HistoryChecker); + +/***/ }), + /***/ "./src/system/jimp.min.js": /*!********************************!*\ !*** ./src/system/jimp.min.js ***! diff --git a/photoshop/src/panels/Main.jsx b/photoshop/src/panels/Main.jsx index c521bcb..d4c5ed7 100644 --- a/photoshop/src/panels/Main.jsx +++ b/photoshop/src/panels/Main.jsx @@ -1,5 +1,6 @@ import React from "react"; import ComfyConnection from "../system/ComfyConnection"; +import HistoryChecker from "../system/HistoryChecker"; import { storage } from "uxp"; export default class Main extends React.Component { @@ -16,6 +17,14 @@ export default class Main extends React.Component { isConnected: instance?.isConnected, comfyURL: instance ? instance.comfyURL : '' }) + if (instance?.isConnected) { + const hc = HistoryChecker.createInstance(); + hc.setChangeCallback((historyId) => { + instance.pushData({history_state_id: historyId}) + }); + } else { + HistoryChecker.instance?.destroy(); + } }); storage.secureStorage.getItem('comfyURL').then((value) => { if (!value) return diff --git a/photoshop/src/system/ComfyConnection.js b/photoshop/src/system/ComfyConnection.js index f542272..1185987 100644 --- a/photoshop/src/system/ComfyConnection.js +++ b/photoshop/src/system/ComfyConnection.js @@ -131,6 +131,18 @@ class ComfyConnection { this.connect(); } + pushData(data) { + if (!this.socket || this.socket.readyState != WebSocket.OPEN) { + console.error('Connection not open'); + return; + } + try{ + this.socket.send(JSON.stringify({ + push_data: data, + })); + } catch (e) { console.error(e) } + } + async onMessage(event) { console.log("Message from comfy ", event.data); try { @@ -280,14 +292,16 @@ class ComfyConnection { } else if (payload.action == 'get_active_history_state_id') { try { - const historyStates = app.activeDocument.historyStates; - const historyState = historyStates[historyStates.length - 1]; + let result = {}; + const historyStates = app.activeDocument?.historyStates; + if (historyStates && historyStates.length > 0) { + const historyState = historyStates[historyStates.length - 1]; + result.history_state_id = historyState.id; + } this.socket.send( JSON.stringify({ call_id: payload.call_id, - result: { - history_state_id: historyState.id, - } + result: result }) ) } catch (e) { @@ -326,6 +340,7 @@ class ComfyConnection { this._isConnected = false; ComfyConnection._callConnectStateChange(); }); + socket.addEventListener('error', (event) => { console.log("Connection error", event); this.reconnectTimer = setTimeout(() => { diff --git a/photoshop/src/system/HistoryChecker.js b/photoshop/src/system/HistoryChecker.js new file mode 100644 index 0000000..06f4b20 --- /dev/null +++ b/photoshop/src/system/HistoryChecker.js @@ -0,0 +1,46 @@ +import {app, action} from "photoshop"; + +class HistoryChecker { + static instance = null; + static createInstance() { + if (HistoryChecker.instance) { + return HistoryChecker.instance; + } + return new HistoryChecker(); + } + constructor() { + HistoryChecker.instance = this; + this.lastCheckId = -1; + this.changeCallback = null; + // this.timer = setInterval(() => { + // this.checkHistoryState(); + // }, 1000) + action.addNotificationListener("historyStateChanged", () => { + this.checkHistoryState(); + }); + } + + destroy() { + clearInterval(this.timer); + this.timer = null; + this.changeCallback = null; + HistoryChecker.instance = null; + } + + setChangeCallback(callback) { + this.changeCallback = callback; + } + + checkHistoryState() { + const historyStates = app.activeDocument?.historyStates; + if (!historyStates || historyStates.length == 0) return; + const historyState = historyStates[historyStates.length - 1]; + const historyId = historyState.id; + if (this.lastCheckId == historyId) return; + this.lastCheckId = historyId; + if (this.changeCallback) + this.changeCallback(historyId); + } +} + +export default HistoryChecker; \ No newline at end of file