From 209cac2fe8f15c352d8c5bb3e7479b2b480a5ae4 Mon Sep 17 00:00:00 2001 From: Raykosan <71210053+Raykosan@users.noreply.github.com> Date: Thu, 9 Jul 2026 21:33:12 +0300 Subject: [PATCH] Code optimization --- Rayko_Intermediate_Spline_Mask.py | 6 ------ Rayko_MultiLatent.py | 7 ++----- Rayko_RGB2RGBA.py | 4 ---- Rayko_Text_Overlay_Pro.py | 5 ----- web/rs_collage.js | 25 ------------------------- web/rs_compare.js | 2 -- web/rs_insert_crop.js | 3 --- web/rs_multilatent.js | 2 -- web/rs_outpaint.js | 18 ------------------ web/rs_overlay_pro.js | 1 - web/rs_prompts.js | 4 ---- web/rs_styles.js | 22 ---------------------- web/spline_mask.js | 14 -------------- 13 files changed, 2 insertions(+), 111 deletions(-) diff --git a/Rayko_Intermediate_Spline_Mask.py b/Rayko_Intermediate_Spline_Mask.py index 647800d..20e1e76 100644 --- a/Rayko_Intermediate_Spline_Mask.py +++ b/Rayko_Intermediate_Spline_Mask.py @@ -64,17 +64,14 @@ class RaykoIntermediateSplineMask: def create_mask(self, image, coordinates="[]", unique_id=None, prompt_id=None): unique_id = str(unique_id) if unique_id else "unknown" - # Проверка batch mode saved_preset = _SAVED_MASK_PRESETS.get(unique_id, {}) batch_mode_active = saved_preset.get("active", False) if SERVER_AVAILABLE and unique_id: - # Если batch активен и есть сохраненные координаты - используем их без паузы if batch_mode_active and saved_preset.get("coordinates"): coordinates = saved_preset["coordinates"] print(f"[InSPLINE ] Batch mode: using saved mask for node {unique_id}") - # ← ДОБАВЛЕНО: Сохраняем изображение и отправляем событие для обновления UI if prompt_id is None: import uuid prompt_id = str(uuid.uuid4()) @@ -97,7 +94,6 @@ class RaykoIntermediateSplineMask: except Exception as e: print(f"[InSPLINE 🦊] Error saving image in batch mode: {e}") else: - # Обычный режим с паузой if unique_id not in PENDING_DECISIONS: PENDING_DECISIONS[unique_id] = { "status": "pending", @@ -204,7 +200,6 @@ if SERVER_AVAILABLE: PENDING_DECISIONS[node_id]["status"] = "approved" if coordinates: PENDING_DECISIONS[node_id]["coordinates"] = coordinates - # Сохраняем preset если batch_mode активен if batch_mode and coordinates: _SAVED_MASK_PRESETS[node_id] = { "coordinates": coordinates, @@ -218,7 +213,6 @@ if SERVER_AVAILABLE: elif decision == "cancel": PENDING_DECISIONS[node_id]["status"] = "cancelled" - # Очищаем preset при cancel if node_id in _SAVED_MASK_PRESETS: del _SAVED_MASK_PRESETS[node_id] diff --git a/Rayko_MultiLatent.py b/Rayko_MultiLatent.py index 3bc05fc..d7cfb44 100644 --- a/Rayko_MultiLatent.py +++ b/Rayko_MultiLatent.py @@ -107,9 +107,9 @@ class RS_Image_MultiLatent: RETURN_NAMES = ("latent", "width_px", "height_px") FUNCTION = "generate" CATEGORY = "🦊 RaykoStudio" + DESCRIPTION = "Universal latent generation node - automatically adapts to any VAE architecture." def generate(self, vae, size_mode, active_preset, preset_standard, preset_qwen, preset_krea2, width, height, megapixels, aspect_ratio, batch_size): - # Определение параметров из VAE latent_channels = 4 scale_factor = 8 @@ -128,7 +128,6 @@ class RS_Image_MultiLatent: except: pass - # Валидация if latent_channels <= 0 or latent_channels > 256: raise ValueError(f"Invalid latent_channels: {latent_channels}") if scale_factor <= 0 or scale_factor > 64: @@ -136,9 +135,7 @@ class RS_Image_MultiLatent: print(f"[RS_Image_MultiLatent] VAE: {latent_channels} channels, {scale_factor}x scale. Active: {active_preset}") - # Расчёт размеров if size_mode == "Preset": - # Выбираем значение из активного списка if active_preset == "Standard": preset = preset_standard elif active_preset == "Qwen": @@ -147,7 +144,7 @@ class RS_Image_MultiLatent: preset = preset_krea2 size_map = { - # Standard & Krea2 (одинаковые размеры) + # Standard & Krea2 "Square - 512x512 (1:1)": (512, 512), "Square - 640x640 (1:1)": (640, 640), "Square - 768x768 (1:1)": (768, 768), "Square - 1024x1024 (1:1)": (1024, 1024), "Square - 1280x1280 (1:1)": (1280, 1280), "Square - 1536x1536 (1:1)": (1536, 1536), diff --git a/Rayko_RGB2RGBA.py b/Rayko_RGB2RGBA.py index efd3ed7..7717a6f 100644 --- a/Rayko_RGB2RGBA.py +++ b/Rayko_RGB2RGBA.py @@ -39,17 +39,13 @@ class LoadImageRGBA: def load_image(self, image): image_path = os.path.join(folder_paths.get_input_directory(), image) - # Загружаем изображение в PIL img = Image.open(image_path) - # Принудительно конвертируем в RGBA, чтобы гарантировать 4 канала if img.mode != 'RGBA': img = img.convert('RGBA') - # Конвертируем в numpy array (нормализуем 0-1) img_array = np.array(img).astype(np.float32) / 255.0 - # Добавляем размерность батча [1, H, W, C] tensor = torch.from_numpy(img_array).unsqueeze(0) print(f"[rgb2rgba ] Loaded {image}: {img.size}, mode={img.mode}") diff --git a/Rayko_Text_Overlay_Pro.py b/Rayko_Text_Overlay_Pro.py index 03f0edf..a4e8309 100644 --- a/Rayko_Text_Overlay_Pro.py +++ b/Rayko_Text_Overlay_Pro.py @@ -969,7 +969,6 @@ class RS_OverlayPro: text_mask = masks['text_mask'] outline_mask = masks['outline_mask'] - # Извлекаем shadow-параметры enable_shadow = bool(text_params.get('enable_shadow', False)) shadow_color = text_params.get('shadow_color', '#333333') shadow_opacity = min(1.0, max(0.0, float(text_params.get('shadow_opacity', 0.8)))) @@ -1005,7 +1004,6 @@ class RS_OverlayPro: result = Image.new("RGBA", render_size, (0,0,0,0)) - # 1. GLOW if include_glow and config.enable_glow and config.glow_size > 0: glow_mask = self._apply_glow_to_mask( text_mask, config.glow_size, config.glow_spread, config.glow_opacity @@ -1013,7 +1011,6 @@ class RS_OverlayPro: glow_colored = self._apply_color_to_mask(glow_mask, config.glow_color) result = Image.alpha_composite(result, glow_colored) - # 2. SHADOW (под текстом и outline) if config.enable_shadow: shadow_mask = self._apply_shadow_to_mask( text_mask, config.shadow_blur, config.shadow_offset_x, config.shadow_offset_y @@ -1021,12 +1018,10 @@ class RS_OverlayPro: shadow_colored = self._apply_color_to_mask(shadow_mask, config.shadow_color) result = Image.alpha_composite(result, shadow_colored) - # 3. OUTLINE if config.has_outline: outline_colored = self._apply_color_to_mask(outline_mask, config.outline_color) result = Image.alpha_composite(result, outline_colored) - # 4. TEXT text_colored = self._apply_color_to_mask(text_mask, config.text_color) result = Image.alpha_composite(result, text_colored) diff --git a/web/rs_collage.js b/web/rs_collage.js index 7fb519f..205edf6 100644 --- a/web/rs_collage.js +++ b/web/rs_collage.js @@ -13,13 +13,11 @@ app.registerExtension({ nodeType.prototype.onNodeCreated = function() { if (onNodeCreated) onNodeCreated.apply(this, arguments); - // State this.overlay = { x: 0, y: 0, width: 100, height: 100, rotation: 0, flipH: false, flipV: false }; this.overlayRelative = { x: 0.5, y: 0.5, width: 0.3, height: 0.3, rotation: 0, flipH: false, flipV: false }; this.realOverlay = { width: 0, height: 0 }; this.realBackground = { width: 0, height: 0 }; - // View this.displayWidth = 420; this.displayHeight = 420; this.canvasPixelSize = 420; @@ -27,7 +25,6 @@ app.registerExtension({ this.viewOffsetX = 0; this.viewOffsetY = 0; - // Images & Editing this.overlayImage = null; this.backgroundImage = null; this.isEditing = false; @@ -36,21 +33,18 @@ app.registerExtension({ this.dragState = null; this.currentSessionTimestamp = null; - // Parameters this.opacity = 1.0; this.featherType = "None"; this.blurRadius = 50; this.blurHardness = 0; this.advancedMode = false; - // Overlay DOM this.overlayContainer = null; this.overlayCanvas = null; this.overlayCtx = null; this.overlayRenderLoop = null; this.overlayInputs = {}; - // Misc this.featherCenter = { x: 0.5, y: 0.5 }; this.canvasRealWidth = 0; this.canvasRealHeight = 0; @@ -68,10 +62,8 @@ app.registerExtension({ this.previewMaxSize = 512; this.pendingEditorData = null; - // Heartbeat this.heartbeatInterval = null; - // Helper function to sync widget values const syncWidgetValue = (widgetName, value) => { const widget = this.widgets?.find(w => w.name === widgetName); if (widget) { @@ -79,19 +71,16 @@ app.registerExtension({ } }; - // Hide default widgets ["opacity", "feather_type", "blur_radius", "blur_hardness"].forEach(n => { const w = this.widgets?.find(w => w.name === n); if (w) w.hidden = true; }); - // Add custom widgets this.addWidget("slider", "opacity", 1.0, v => { this.opacity = v; syncWidgetValue("opacity", v); this.previewDirty = true; this.setDirtyCanvas(true); }, { min: 0, max: 1, step: 0.01 }); this.addWidget("combo", "feather_type", "None", v => { this.featherType = v; syncWidgetValue("feather_type", v); this.previewDirty = true; this.setDirtyCanvas(true); }, { values: ["None", "Radial Blur In", "Radial Blur Out", "Ellipse Blur In", "Ellipse Blur Out"] }); this.addWidget("slider", "blur_radius", 50, v => { this.blurRadius = v; syncWidgetValue("blur_radius", v); this.previewDirty = true; this.setDirtyCanvas(true); }, { min: 0, max: 100, step: 1 }); this.addWidget("slider", "blur_hardness", 0, v => { this.blurHardness = v; syncWidgetValue("blur_hardness", v); this.previewDirty = true; this.setDirtyCanvas(true); }, { min: 0, max: 100, step: 1 }); - // Heartbeat methods this.startHeartbeat = function() { if (this.heartbeatInterval) clearInterval(this.heartbeatInterval); this.heartbeatInterval = setInterval(async () => { @@ -112,7 +101,6 @@ app.registerExtension({ } }; - // Create Overlay DOM if (!this.overlayContainer) { this.overlayContainer = document.createElement('div'); this.overlayContainer.style.cssText = 'position:fixed;top:60px;left:0;right:0;bottom:0;background:rgba(10,10,10,0.96);z-index:999;display:none;flex-direction:row;align-items:stretch;font-family:system-ui,-apple-system,sans-serif;'; @@ -216,7 +204,6 @@ app.registerExtension({ this._resizeObserver.observe(this.overlayCanvasWrapper); } - // API Listeners api.addEventListener("rs-collage-start", (event) => { if (event.detail.id != this.id) return; this.pendingEditorData = event.detail; this.openDeferredEditor(); }); api.addEventListener("rs-collage-ready", (event) => { if (event.detail.id != this.id) return; this.pendingEditorData = event.detail; }); api.addEventListener("interrupted", () => { @@ -229,7 +216,6 @@ app.registerExtension({ this.setDirtyCanvas(true); }); - // Cleanup on node removal const origOnRemoved = this.onRemoved; this.onRemoved = function() { this.stopHeartbeat(); @@ -244,7 +230,6 @@ app.registerExtension({ }; }; - // Helpers nodeType.prototype._syncOverlayUI = function() { if (!this.advancedMode) return; this.overlayInputs.opacity.value = Math.round(this.opacity * 100); @@ -349,7 +334,6 @@ app.registerExtension({ this.computeAndApplyView(); }); - // Mouse handlers this._overlayMouseHandler = (e) => this._handleOverlayEvent(e, e.type === 'mousedown' ? 'down' : e.type === 'mousemove' ? 'move' : 'up'); this._overlayMouseLeaveHandler = () => this.onMouseUp(); this.overlayCanvas.addEventListener('mousedown', this._overlayMouseHandler); @@ -357,11 +341,9 @@ app.registerExtension({ this.overlayCanvas.addEventListener('mouseup', this._overlayMouseHandler); this.overlayCanvas.addEventListener('mouseleave', this._overlayMouseLeaveHandler); - // Wheel handler (passive: false is mandatory for preventDefault) this._overlayWheelHandler = (e) => this._handleOverlayWheel(e); this.overlayCanvas.addEventListener('wheel', this._overlayWheelHandler, { passive: false }); - // Keyboard this._globalKeyHandler = (e) => { if (e.key === 'Escape' && activeRSCollageNode === this) { this.sendTransforms(); @@ -370,7 +352,6 @@ app.registerExtension({ }; window.addEventListener('keydown', this._globalKeyHandler); - // Render loop (clears canvas before drawing to prevent phantom) this.overlayRenderLoop = () => { if (!this.advancedMode || !this.overlayCtx) return; this.overlayCtx.clearRect(0, 0, this.overlayCanvas.width, this.overlayCanvas.height); @@ -380,7 +361,6 @@ app.registerExtension({ requestAnimationFrame(this.overlayRenderLoop); }; - // Dedicated overlay drawing method (separated from node drawing) nodeType.prototype.drawOverlayCanvas = function(ctx) { if (!this.isEditing || !this.backgroundImage) { ctx.fillStyle = "#888"; @@ -481,7 +461,6 @@ app.registerExtension({ this.featherCenter = { x: 0.5, y: 0.5 }; this.previewDirty = true; - // Sync widget values when opening editor const syncWidgetValue = (widgetName, value) => { const widget = this.widgets?.find(w => w.name === widgetName); if (widget) { @@ -652,8 +631,6 @@ app.registerExtension({ }; nodeType.prototype.sendTransforms = async function() { - // Инвертируем координаты центра размытия при флипе, - // т.к. в Python feather применяется к уже отражённому изображению const fcx = this.overlay.flipH ? 1.0 - this.featherCenter.x : this.featherCenter.x; const fcy = this.overlay.flipV ? 1.0 - this.featherCenter.y : this.featherCenter.y; @@ -710,7 +687,6 @@ app.registerExtension({ return { cSize, rectX, rectY }; }; - // Node foreground drawing (ONLY for normal mode) nodeType.prototype.onDrawForeground = function(ctx) { if (this.advancedMode) { ctx.clearRect(0, 0, this.size[0], this.size[1]); @@ -863,7 +839,6 @@ app.registerExtension({ } }); -// Глобальный обработчик закрытия вкладки window.addEventListener("beforeunload", () => { const nodes = (app.graph && app.graph._nodes) ? app.graph._nodes.filter(n => n.type === "RSCollage") diff --git a/web/rs_compare.js b/web/rs_compare.js index 743b6a6..2efa608 100644 --- a/web/rs_compare.js +++ b/web/rs_compare.js @@ -154,7 +154,6 @@ app.registerExtension({ if ((this.flags && this.flags.collapsed) || (!this.rs_img1 && !this.rs_img2)) return r; - // Сохраняем состояние Canvas, чтобы не сломать нативную отрисовку LiteGraph ctx.save(); const getVal = (name, def) => { @@ -316,7 +315,6 @@ app.registerExtension({ this.rs_resetAllRect = { x: padding, y: resetAllY, w: buttonWidth, h: resetAllHeight }; - // Восстанавливаем состояние Canvas, чтобы не сломать нативную отрисовку LiteGraph ctx.restore(); return r; diff --git a/web/rs_insert_crop.js b/web/rs_insert_crop.js index 289310f..4295dcc 100644 --- a/web/rs_insert_crop.js +++ b/web/rs_insert_crop.js @@ -39,13 +39,11 @@ app.registerExtension({ } }; - // ⚠️ Сбрасываем статус ПЕРЕД выполнением node.onExecuted = function(message) { this.currentStatus = "✅ Inserted"; this.setDirtyCanvas(true, true); }; - // ⚠️ Обновляем статус при запуске workflow const onDrawForeground = node.onDrawForeground; node.onDrawForeground = function(ctx) { if (!this.flags.collapsed) { @@ -64,7 +62,6 @@ app.registerExtension({ }; }, - // ⚠️ Глобальный слушатель начала выполнения setup(app) { app.api.addEventListener("executing", (event) => { const nodeId = event.detail; diff --git a/web/rs_multilatent.js b/web/rs_multilatent.js index acbaa61..67be8a7 100644 --- a/web/rs_multilatent.js +++ b/web/rs_multilatent.js @@ -11,7 +11,6 @@ app.registerExtension({ const result = onNodeCreated ? onNodeCreated.apply(this) : undefined; initCustomPresets(this); - // === ДОБАВЛЯЕМ ОГРАНИЧЕНИЕ МИНИМАЛЬНОГО РАЗМЕРА === const minWidth = 240; const minHeight = 310; const origOnResize = this.onResize; @@ -25,7 +24,6 @@ app.registerExtension({ this.setDirtyCanvas(true, true); } }; - // ================================================ return result; }; diff --git a/web/rs_outpaint.js b/web/rs_outpaint.js index 2c3afa9..49b764d 100644 --- a/web/rs_outpaint.js +++ b/web/rs_outpaint.js @@ -31,7 +31,6 @@ function setUIActive(dom, isActive) { const pointerEvents = isActive ? "auto" : "none"; const filter = isActive ? "none" : "grayscale(100%)"; - // Кнопки пресетов dom.savePresetBtn.style.opacity = opacity; dom.savePresetBtn.style.pointerEvents = pointerEvents; dom.savePresetBtn.style.filter = filter; @@ -40,38 +39,32 @@ function setUIActive(dom, isActive) { dom.applyPresetBtn.style.pointerEvents = pointerEvents; dom.applyPresetBtn.style.filter = filter; - // Кнопки соотношений (chipBtns) for (const btn of dom.chipBtns) { btn.style.opacity = opacity; btn.style.pointerEvents = pointerEvents; btn.style.filter = filter; } - // Кнопки snap for (const btn of dom.snapBtns) { btn.style.opacity = opacity; btn.style.pointerEvents = pointerEvents; btn.style.filter = filter; } - // Кнопка замка dom.arBtn.style.opacity = opacity; dom.arBtn.style.pointerEvents = pointerEvents; dom.arBtn.style.filter = filter; - // Кнопка Reset dom.resetBtn.style.opacity = opacity; dom.resetBtn.style.pointerEvents = pointerEvents; dom.resetBtn.style.filter = filter; - // Поля ввода W/H dom.wInput.style.opacity = opacity; dom.wInput.style.pointerEvents = pointerEvents; dom.hInput.style.opacity = opacity; dom.hInput.style.pointerEvents = pointerEvents; - // Цвета (mask) dom.maskColorInput.picker.style.opacity = opacity; dom.maskColorInput.picker.style.pointerEvents = pointerEvents; dom.maskColorInput.hexInput.style.opacity = opacity; @@ -79,7 +72,6 @@ function setUIActive(dom, isActive) { dom.maskColorInput.swatch.style.opacity = opacity; dom.maskColorInput.swatch.style.pointerEvents = pointerEvents; - // Цвета (bg) dom.bgColorInput.picker.style.opacity = opacity; dom.bgColorInput.picker.style.pointerEvents = pointerEvents; dom.bgColorInput.hexInput.style.opacity = opacity; @@ -87,20 +79,17 @@ function setUIActive(dom, isActive) { dom.bgColorInput.swatch.style.opacity = opacity; dom.bgColorInput.swatch.style.pointerEvents = pointerEvents; - // Batch кнопка dom.batchBtn.style.opacity = opacity; dom.batchBtn.style.pointerEvents = pointerEvents; dom.batchBtn.style.filter = filter; } function resetNodeState(st, dom, widgets) { - // Сохраняем пользовательские цвета перед сбросом, чтобы не терять выбор const savedMaskColor = st.maskColor; const savedBgColor = st.bgColor; Object.assign(st, createState()); - // Восстанавливаем пользовательские цвета st.maskColor = savedMaskColor; st.bgColor = savedBgColor; @@ -147,7 +136,6 @@ function initLayout(st, wrapEl) { st.cr = srcToCr(def, st.sf, st.scale); st.cropAR = defW / defH; - // Output resolution теперь всегда 0 (используем размер рамки) st.outW = 0; st.outH = 0; st.arLocked = false; @@ -238,7 +226,6 @@ function buildUI() { const snapBtns = SNAP_DEFS.map(([key, label], i) => { const isFirst = i===0, isLast = i===SNAP_DEFS.length-1; const radius = isFirst ? "4px 0 0 4px" : (isLast ? "0 4px 4px 0" : "0"); const bl = isFirst ? "1px solid #444" : "none"; const b = mkEl("button", `padding:2px 7px;font-size:10px;border:1px solid #444;border-left:${bl};border-radius:${radius};background:#2a2a2a;color:#bbb;cursor:pointer;white-space:nowrap;`); b.textContent = label; b.dataset.snap = key; return b; }); snapRow.append(snapLabel, ...snapBtns); - // Удален виджет output_resolution из UI ctrl.append(cropSizeRow, presetRow, snapRow); const maskColorInput = mkColorInput("mask:", "#ff0000"); @@ -274,7 +261,6 @@ function render(st, dom) { setMask(dom.maskTop, cr.x, cr.y, cr.w, Math.max(0, iy1 - cr.y)); setMask(dom.maskBot, cr.x, iy2, cr.w, Math.max(0, cr.y + cr.h - iy2)); setMask(dom.maskLeft, cr.x, iy1, Math.max(0, ix1 - cr.x), iy2 - iy1); setMask(dom.maskRight, ix2, iy1, Math.max(0, cr.x + cr.w - ix2), iy2 - iy1); const padT = Math.max(0, -s.y), padB = Math.max(0, s.y + s.h - srcH), padL = Math.max(0, -s.x), padR = Math.max(0, s.x + s.w - srcW); const outW = Math.round(s.w), outH = Math.round(s.h); - // Выходной размер теперь всегда равен размеру рамки (0 означает авто в бэкенде) dom.sizeLabel.textContent = `${outW} × ${outH}`; dom.sizeLabel.style.display = "block"; dom.sizeLabel.style.left = (cr.x + cr.w / 2) + "px"; dom.sizeLabel.style.top = (cr.y + cr.h - 18) + "px"; const showPad = (el, text, show, cx, cy) => { el.textContent = text; el.style.display = show ? "block" : "none"; if (show) { el.style.left = cx + "px"; el.style.top = cy + "px"; el.style.transform = "translate(-50%,-50%)"; } }; @@ -293,7 +279,6 @@ function render(st, dom) { function fitCropInView(st, dom) { const wrapW = dom.wrap.clientWidth, wrapH = dom.wrap.clientHeight; if (wrapW <= 0 || wrapH <= 0) return; const pad = MARGIN; const bx1 = Math.min(st.sf.x, st.cr.x), by1 = Math.min(st.sf.y, st.cr.y), bx2 = Math.max(st.sf.x + st.sf.w, st.cr.x + st.cr.w), by2 = Math.max(st.sf.y + st.sf.h, st.cr.y + st.cr.h); const bw = bx2 - bx1, bh = by2 - by1; const fitZoom = Math.min((wrapW - pad * 2) / bw, (wrapH - pad * 2) / bh); const needsZoomOut = fitZoom < st.view.zoom; const z = needsZoomOut ? Math.max(0.15, fitZoom) : st.view.zoom; const scL = bx1 * z + st.view.panX, scT = by1 * z + st.view.panY, scR = bx2 * z + st.view.panX, scB = by2 * z + st.view.panY; const outOfBounds = scL < 0 || scT < 0 || scR > wrapW || scB > wrapH; if (!needsZoomOut && !outOfBounds) return; st.view.zoom = z; st.view.panX = (wrapW - bw * z) / 2 - bx1 * z; st.view.panY = (wrapH - bh * z) / 2 - by1 * z; } function syncWidgets(st, widgets, node) { const s = quantizeSrc(crToSrc(st.cr, st.sf, st.scale)); const hex = st.maskColor.replace("#", ""); const mR = parseInt(hex.substring(0,2), 16), mG = parseInt(hex.substring(2,4), 16), mB = parseInt(hex.substring(4,6), 16); - // outW/outH всегда 0, чтобы бэкенд брал размеры рамки if (widgets.cropState) widgets.cropState.value = `${s.x},${s.y},${s.w},${s.h},0,0,${mR},${mG},${mB}`; if (node.graph) node.graph.setDirtyCanvas(true, true); } function setArLocked(st, dom, locked) { st.arLocked = locked; if (locked && st.cr.h > 0) st.cropAR = st.cr.w / st.cr.h; dom.arBtn.textContent = locked ? "🔒" : "🔓"; dom.arBtn.style.border = `1px solid ${locked ? "#99c0ee" : "#444"}`; dom.arBtn.style.background = locked ? "#1a3a5a" : "#2a2a2a"; dom.arBtn.style.color = locked ? "#aadaff" : "#bbb"; dom.arBtn._active = locked; } @@ -324,7 +309,6 @@ function wireInteractions(st, dom, widgets, node, nodeId) { const s = quantizeSrc(crToSrc(st.cr, st.sf, st.scale)); const hex = st.maskColor.replace("#", ""); const mR = parseInt(hex.substring(0,2), 16), mG = parseInt(hex.substring(2,4), 16), mB = parseInt(hex.substring(4,6), 16); - // outW/outH всегда 0 const presetData = { crop_state: `${s.x},${s.y},${s.w},${s.h},0,0,${mR},${mG},${mB}`, maskColor: st.maskColor, bgColor: st.bgColor, outW: 0, outH: 0, cropAR: st.cropAR, arLocked: st.arLocked }; fetch("/rs_outpaint/save_preset", { method: "POST", @@ -444,10 +428,8 @@ function wireInteractions(st, dom, widgets, node, nodeId) { setArLocked(st, dom, true); st.cropAR = targetAR; - // Рассчитываем новые размеры от ИСХОДНИКА const { w: newW, h: newH } = calcSizeByRatio(st.srcW, st.srcH, targetAR); - // Центрируем относительно ИСХОДНОГО изображения const centerX = st.srcW / 2; const centerY = st.srcH / 2; diff --git a/web/rs_overlay_pro.js b/web/rs_overlay_pro.js index 38754ec..9428b2d 100644 --- a/web/rs_overlay_pro.js +++ b/web/rs_overlay_pro.js @@ -537,7 +537,6 @@ app.registerExtension({ const btnCancel = makeBtn("❌ CANCEL", "#dc3545", () => { this.cancelEditing(); this._toggleAdvancedMode(); }); buttonContainer.appendChild(btnCancel); - // TEXT секция (всегда раскрыта, без чекбокса) const textHeader = document.createElement('div'); textHeader.style.cssText = 'display:flex;align-items:center;gap:8px;padding:8px 0;'; const textChevron = document.createElement('span'); diff --git a/web/rs_prompts.js b/web/rs_prompts.js index 14dbaf4..5136936 100644 --- a/web/rs_prompts.js +++ b/web/rs_prompts.js @@ -6,7 +6,6 @@ let pendingDeleteName = null; const styleBlock = document.createElement('style'); styleBlock.innerHTML = ` - /* Стили ТОЛЬКО для кастомного textarea ноды RSPrompts */ .rs-custom-textarea { flex: 1; width: 100%; @@ -23,21 +22,18 @@ styleBlock.innerHTML = ` box-sizing: border-box; } - /* Светлая тема ComfyUI (классический UI) */ body.light-mode .rs-custom-textarea { background: #fff; color: #000; border-color: #ccc; } - /* Светлая тема нового UI ComfyUI */ html[data-theme="light"] .rs-custom-textarea { background: #fff; color: #000; border-color: #ccc; } - /* Оверлей ожидания */ .rs-waiting-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; diff --git a/web/rs_styles.js b/web/rs_styles.js index f7379c5..1a966af 100644 --- a/web/rs_styles.js +++ b/web/rs_styles.js @@ -12,7 +12,6 @@ app.registerExtension({ nodeType.prototype.onNodeCreated = function() { const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; - // Initialize data this.data = { active_csv_file: "", styles: [], @@ -29,7 +28,6 @@ app.registerExtension({ const self = this; - // Find hidden widget this.hiddenWidget = this.widgets.find(w => w.name === "node_data"); if (this.hiddenWidget) { this.hiddenWidget.hidden = true; @@ -41,15 +39,12 @@ app.registerExtension({ this.hiddenWidget.element.style.pointerEvents = "none"; } - // CRITICAL FIX: Load data from widget value try { const savedData = JSON.parse(this.hiddenWidget.value || "{}"); if (savedData && typeof savedData === 'object') { - // Restore saved data, but keep csv_list if empty this.data = { ...this.data, ...savedData, - // Ensure csv_list doesn't override with empty from save csv_list: this.data.csv_list.length > 0 ? this.data.csv_list : savedData.csv_list || [] }; } @@ -57,14 +52,12 @@ app.registerExtension({ console.error("[Rayko] Error loading saved data", e); } - // Override serializeValue to always return current data this.hiddenWidget.serializeValue = () => { this.syncData(); return JSON.stringify(this.data); }; } - // Hide all other widgets if (this.widgets) { this.widgets.forEach(w => { if (w.name !== "node_data") { @@ -75,7 +68,6 @@ app.registerExtension({ this.setSize([this.targetWidth, 300]); - // --- Core Data Loader --- this.loadCSVList = async function() { try { const response = await api.fetchApi("/rayko_get_csv_files"); @@ -94,7 +86,6 @@ app.registerExtension({ } }; - // --- Helper Drawing Methods --- this.drawSeparator = function(ctx, text, x, y, w, h) { ctx.fillStyle = "#444"; ctx.fillRect(x, y + 8, w, 1); @@ -155,7 +146,6 @@ app.registerExtension({ ctx.fillText(label, x + w/2 + 8, y + h/2); }; - // --- Main Rendering --- this.onDrawForeground = function(ctx, visibleRect) { this.clickZones = []; const startY = 80; @@ -234,7 +224,6 @@ app.registerExtension({ } }; - // --- Input Handling --- this.onMouseDown = function(e, pos, canvas) { if (!this.clickZones.length) return false; @@ -273,7 +262,6 @@ app.registerExtension({ return false; }; - // --- Popup: CSV Selector --- this.showCSVSelector = function(clickEvent) { const list = self.data.csv_list || []; if (!list.length) return; @@ -324,7 +312,6 @@ app.registerExtension({ document.addEventListener("keydown", closeEsc, true); }; - // --- Upload Logic --- this.uploadCSVFile = async function() { const fileInput = document.createElement("input"); fileInput.type = "file"; @@ -361,7 +348,6 @@ app.registerExtension({ fileInput.click(); }; - // --- Style Selector Trigger --- this.showStyleSelector = async function(clickEvent) { if (!self.data.active_csv_file || self.data.active_csv_file === "No CSV files") { alert("⚠️ Upload CSV first!"); @@ -421,7 +407,6 @@ app.registerExtension({ } }; - // --- Popup: Style Tree Menu --- this.showStyleTreeMenu = function(tree, expandedFolders, fixedPosition) { const currentScroll = self.menuScrollPosition || 0; @@ -551,7 +536,6 @@ app.registerExtension({ document.addEventListener("keydown", closeEsc, true); }; - // --- Core Logic --- this.addStyleRow = function(styleFullName, styleDisplayName) { const exists = self.data.styles.some(row => row.name === styleFullName && row.file === self.data.active_csv_file); if (exists) { alert("⚠️ Already added!"); return; } @@ -567,7 +551,6 @@ app.registerExtension({ if (this.hiddenWidget) { this.hiddenWidget.value = JSON.stringify(this.data); } - // Mark graph as changed to trigger auto-save if (this.graph) { this.graph.changeTracker?.dispatchEvent(new Event("change")); } @@ -581,7 +564,6 @@ app.registerExtension({ } }; - // CRITICAL: Override serialize method to ensure data is saved const onSerialize = this.onSerialize; this.onSerialize = function(o) { self.syncData(); @@ -590,19 +572,16 @@ app.registerExtension({ } }; - // CRITICAL: Override onConfigure to restore data after load const onConfigure = this.onConfigure; this.onConfigure = function(o) { if (onConfigure) { onConfigure.apply(this, arguments); } - // Restore data from widget after configuration if (this.hiddenWidget && this.hiddenWidget.value) { try { const restoredData = JSON.parse(this.hiddenWidget.value); if (restoredData) { this.data = { ...this.data, ...restoredData }; - // Don't lose csv_list if empty if (!this.data.csv_list.length && restoredData.csv_list) { this.data.csv_list = restoredData.csv_list; } @@ -615,7 +594,6 @@ app.registerExtension({ } }; - // Load CSV list after everything is set up this.loadCSVList(); return result; diff --git a/web/spline_mask.js b/web/spline_mask.js index e65769f..b26a6d8 100644 --- a/web/spline_mask.js +++ b/web/spline_mask.js @@ -15,21 +15,17 @@ app.registerExtension({ const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; const node = this; - // Инициализация данных node.data = { selected_image: "", spline_coords: "[]" }; - // Настройка размеров node.targetWidth = 450; node.setSize([node.targetWidth, 600]); - // Находим скрытые виджеты const imageWidget = node.widgets?.find(w => w.name === "image"); const coordsWidget = node.widgets?.find(w => w.name === "coordinates"); - // Скрываем виджеты if (imageWidget) { imageWidget.hidden = true; if (imageWidget.element) imageWidget.element.style.display = "none"; @@ -39,11 +35,9 @@ app.registerExtension({ if (coordsWidget.element) coordsWidget.element.style.display = "none"; } - // Загружаем сохраненные данные из виджетов if (imageWidget && imageWidget.value) node.data.selected_image = imageWidget.value; if (coordsWidget && coordsWidget.value) node.data.spline_coords = coordsWidget.value; - // Переопределяем serializeValue для виджетов if (imageWidget) { imageWidget.serializeValue = () => node.data.selected_image; } @@ -51,14 +45,12 @@ app.registerExtension({ coordsWidget.serializeValue = () => node.data.spline_coords; } - // Состояние node.image = new Image(); node.imageLoaded = false; node.imageLoading = false; node.buttons = []; node.imageList = []; - // Кнопки node.buttons = [ { label: "🎨 IMAGE", color: "#2196F3", callback: () => node.showImageSelector(), hover: false }, { label: "🖼️ UPLOAD", color: "#4CAF50", callback: () => node.triggerFileUpload(), hover: false }, @@ -148,12 +140,9 @@ app.registerExtension({ return false; }; - // ✅ НОВОЕ: Глобальный перехват дропа с проверкой координат ноды - // Это решает проблему, когда _overlayCanvas скрыт (display: none) const handleCanvasDrop = (e) => { if (!e.dataTransfer || !e.dataTransfer.files.length) return; - // Проверяем, находится ли курсор над этой конкретной нодой const rect = app.canvas.canvas.getBoundingClientRect(); const ds = app.canvas.ds; const nodeX = rect.left + ((node.pos[0] + ds.offset[0]) * ds.scale); @@ -189,11 +178,9 @@ app.registerExtension({ e.clientY >= nodeY && e.clientY <= nodeY + nodeH) { e.preventDefault(); e.stopPropagation(); - // Визуальный фидбек можно добавить здесь, если захочется } }; - // Регистрируем слушатели с capture: true, чтобы перехватить событие ДО глобального обработчика ComfyUI app.canvas.canvas.addEventListener('dragover', handleCanvasDragOver, { capture: true }); app.canvas.canvas.addEventListener('drop', handleCanvasDrop, { capture: true }); @@ -621,7 +608,6 @@ app.registerExtension({ const menu = document.querySelector('.spline-image-menu'); if (menu) menu.remove(); - // ✅ НОВОЕ: Очистка глобальных слушателей при удалении ноды (предотвращает утечки памяти) app.canvas.canvas.removeEventListener('dragover', handleCanvasDragOver, { capture: true }); app.canvas.canvas.removeEventListener('drop', handleCanvasDrop, { capture: true });