Added an RS Upscaler node
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,102 @@
|
||||
import torch
|
||||
import comfy.utils
|
||||
import comfy.model_management
|
||||
import folder_paths
|
||||
|
||||
|
||||
class RSUpscaler:
|
||||
"""
|
||||
🦊 RS Upscaler
|
||||
Комбинирует загрузку модели и апскейл в одной ноде.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
# Эти виджеты будут скрыты JS и заменены на кастомные
|
||||
"upscale_model": (folder_paths.get_filename_list("upscale_models"),),
|
||||
"upscale_method": (["nearest-exact", "bilinear", "area", "bicubic", "lanczos"],),
|
||||
"upscale_x": ("FLOAT", {"default": 2.0, "min": 0.1, "max": 8.0, "step": 0.05}),
|
||||
},
|
||||
"hidden": {
|
||||
# Маркер для JS-расширения
|
||||
"rs_node_type": ("STRING", {"default": "RSUpscaler"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "upscale"
|
||||
CATEGORY = "🦊 RaykoStudio"
|
||||
|
||||
def upscale(self, image, upscale_model, upscale_method, upscale_x, rs_node_type=None):
|
||||
# 1. Загрузка модели
|
||||
model_path = folder_paths.get_full_path("upscale_models", upscale_model)
|
||||
|
||||
try:
|
||||
sd = comfy.utils.load_torch_file(model_path, safe_load=True)
|
||||
from comfy_extras.chainner_models import model_loading
|
||||
|
||||
is_chainner = False
|
||||
for key in sd.keys():
|
||||
if "module.layers" in key or "model.0.weight" in key or "conv_first.weight" in key:
|
||||
is_chainner = True
|
||||
break
|
||||
|
||||
if is_chainner:
|
||||
upscale_model_obj = model_loading.load_state_dict(sd).eval()
|
||||
else:
|
||||
raise ValueError("Не удалось определить архитектуру модели")
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(
|
||||
f"Ошибка загрузки модели '{upscale_model}': {str(e)}\n"
|
||||
f"Убедитесь, что файл является корректной upscale-моделью"
|
||||
)
|
||||
|
||||
# 2. Применение модели с тайлингом
|
||||
device = comfy.model_management.get_torch_device()
|
||||
upscale_model_obj.to(device)
|
||||
|
||||
in_img = image.movedim(-1, -3).to(device)
|
||||
scale = upscale_model_obj.scale
|
||||
|
||||
upscaled_tensor = comfy.utils.tiled_scale(
|
||||
in_img,
|
||||
lambda a: upscale_model_obj(a),
|
||||
tile_x=192,
|
||||
tile_y=192,
|
||||
overlap=8,
|
||||
upscale_amount=scale
|
||||
)
|
||||
|
||||
upscale_model_obj.cpu()
|
||||
comfy.model_management.soft_empty_cache()
|
||||
|
||||
upscaled = torch.clamp(upscaled_tensor.movedim(-3, -1), min=0, max=1.0)
|
||||
|
||||
# 3. Финальный ресайз
|
||||
orig_height = image.shape[1]
|
||||
orig_width = image.shape[2]
|
||||
|
||||
target_width = int(orig_width * upscale_x)
|
||||
target_height = int(orig_height * upscale_x)
|
||||
|
||||
if upscaled.shape[2] != target_width or upscaled.shape[1] != target_height:
|
||||
samples = upscaled.movedim(-1, 1)
|
||||
s = comfy.utils.common_upscale(samples, target_width, target_height, upscale_method, crop="disabled")
|
||||
result = s.movedim(1, -1)
|
||||
else:
|
||||
result = upscaled
|
||||
|
||||
return (result,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"RSUpscaler": RSUpscaler,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RSUpscaler": "🦊 RS Upscaler",
|
||||
}
|
||||
+1
-1
@@ -29,7 +29,7 @@ IGNORED_MODULES: Set[str] = {'__init__.py'}
|
||||
REQUIRED_MODULES: Set[str] = set()
|
||||
|
||||
PACKAGE_DISPLAY_NAME = "ComfyUI_RaykoStudio"
|
||||
__version__ = "0.36.4"
|
||||
__version__ = "0.37.0"
|
||||
|
||||
def load_modules():
|
||||
print(f"🦊 {PACKAGE_DISPLAY_NAME}: Version: {__version__}")
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ComfyUI_RaykoStudio",
|
||||
"version": "0.36.4",
|
||||
"version": "0.37.0",
|
||||
"description": "Set of custom nodes for ComfyUI providing additional image processing capabilities",
|
||||
"author": "Raykosan",
|
||||
"license": "Apache License 2.0",
|
||||
@@ -39,6 +39,7 @@
|
||||
"Rayko_Spline_Mask.py"
|
||||
"Rayko_Styles_Loader.py"
|
||||
"Rayko_Text_Overlay_Pro.py"
|
||||
"Rayko_Upscaler.py"
|
||||
"Rayko_VAE_Save_Image.py"
|
||||
"settings_manager_api.py"
|
||||
]
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "ComfyUI_RaykoStudio"
|
||||
description = "Set of custom nodes for ComfyUI providing additional image processing capabilities"
|
||||
version = "0.36.4"
|
||||
version = "0.37.0"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["torch", "numpy", "Pillow>=10.0.0", "freetype-py>=2.5.1", "pycairo>=1.29.0", "scipy>=1.10.0"]
|
||||
|
||||
|
||||
@@ -0,0 +1,519 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
const RS_UPSCALER_NODE_TYPE = "RSUpscaler";
|
||||
const DEFAULT_SCALE = 2.0;
|
||||
const SCALE_MIN = 0.1;
|
||||
const SCALE_MAX = 8.0;
|
||||
const SCALE_STEP = 0.05;
|
||||
const NODE_WIDTH = 300;
|
||||
const NODE_HEIGHT = 130;
|
||||
|
||||
function getScopedStyles(scopeId) {
|
||||
return `
|
||||
/* === Контейнер === */
|
||||
.${scopeId} .rs-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.${scopeId} .rs-row label {
|
||||
flex-shrink: 0;
|
||||
font-size: 11px;
|
||||
color: #aaa;
|
||||
white-space: nowrap;
|
||||
min-width: 40px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.${scopeId} .rs-row select {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: #222;
|
||||
color: #fff;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
padding: 5px 8px;
|
||||
font-size: 11px;
|
||||
outline: none;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
appearance: auto;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.${scopeId} .rs-row select:hover { border-color: #ff9800; }
|
||||
.${scopeId} .rs-row select:focus { border-color: #ff9800; box-shadow: 0 0 0 1px rgba(255,152,0,0.3); }
|
||||
|
||||
/* === Слайдер-строка === */
|
||||
.${scopeId} .rs-slider-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.${scopeId} .rs-slider-row label {
|
||||
flex-shrink: 0;
|
||||
font-size: 11px;
|
||||
color: #aaa;
|
||||
white-space: nowrap;
|
||||
min-width: 40px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
/* Слайдер: зелёный fill + белый хэндл */
|
||||
.${scopeId} .rs-slider-row input[type="range"] {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: linear-gradient(to right,
|
||||
#4CAF50 0%, #4CAF50 var(--rs-fill, 25%),
|
||||
#444 var(--rs-fill, 25%), #444 100%);
|
||||
}
|
||||
.${scopeId} .rs-slider-row input[type="range"]::-webkit-slider-runnable-track {
|
||||
height: 4px;
|
||||
background: transparent;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.${scopeId} .rs-slider-row input[type="range"]::-moz-range-track {
|
||||
height: 4px;
|
||||
background: transparent;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.${scopeId} .rs-slider-row input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
margin-top: -4px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
.${scopeId} .rs-slider-row input[type="range"]::-moz-range-thumb {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
/* === Кнопки +/- === */
|
||||
.${scopeId} .rs-step-btn {
|
||||
width: 20px;
|
||||
height: 26px;
|
||||
background: #252525;
|
||||
color: #888;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 9px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: 0.15s;
|
||||
flex-shrink: 0;
|
||||
user-select: none;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
}
|
||||
.${scopeId} .rs-step-btn:hover {
|
||||
background: #2a2a2a;
|
||||
color: #fff;
|
||||
border-color: #ff9800;
|
||||
}
|
||||
.${scopeId} .rs-step-btn:active {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
/* === Value input === */
|
||||
.${scopeId} .rs-value-input {
|
||||
width: 35px;
|
||||
min-width: 35px;
|
||||
max-width: 35px;
|
||||
height: 26px;
|
||||
text-align: center;
|
||||
background: #252525;
|
||||
color: #4CAF50;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: 0.15s;
|
||||
font-family: sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.${scopeId} .rs-value-input:hover {
|
||||
border-color: #ff9800;
|
||||
color: #fff;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
// Popup стили — глобальные (popup рендерится в document.body вне контейнера ноды)
|
||||
const POPUP_STYLES = `
|
||||
.rs-value-popup {
|
||||
position: fixed;
|
||||
z-index: 10004;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.rs-value-popup input {
|
||||
width: 80px;
|
||||
background: #222;
|
||||
color: #fff;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
outline: none;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.rs-value-popup input:focus {
|
||||
border-color: #ff9800;
|
||||
}
|
||||
.rs-value-popup button {
|
||||
background: #4CAF50;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.rs-value-popup button:hover {
|
||||
background: #43A047;
|
||||
}
|
||||
`;
|
||||
|
||||
let popupStylesInjected = false;
|
||||
function injectPopupStyles() {
|
||||
if (popupStylesInjected) return;
|
||||
const style = document.createElement("style");
|
||||
style.textContent = POPUP_STYLES;
|
||||
document.head.appendChild(style);
|
||||
popupStylesInjected = true;
|
||||
}
|
||||
|
||||
let upscaleModelsCache = null;
|
||||
async function fetchUpscaleModels() {
|
||||
if (upscaleModelsCache) return upscaleModelsCache;
|
||||
try {
|
||||
const resp = await fetch("/api/object_info/RSUpscaler");
|
||||
const data = await resp.json();
|
||||
upscaleModelsCache = data.RSUpscaler.input.required.upscale_model[0];
|
||||
} catch (e) {
|
||||
console.warn("[RS Upscaler] Failed to fetch models list:", e);
|
||||
upscaleModelsCache = [];
|
||||
}
|
||||
return upscaleModelsCache;
|
||||
}
|
||||
|
||||
function clampScale(v) {
|
||||
return Math.max(SCALE_MIN, Math.min(SCALE_MAX, v));
|
||||
}
|
||||
|
||||
function formatScale(v) {
|
||||
return parseFloat(v).toFixed(2);
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "RaykoStudio.RSUpscaler",
|
||||
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== RS_UPSCALER_NODE_TYPE) return;
|
||||
|
||||
injectPopupStyles();
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const result = onNodeCreated?.apply(this, arguments);
|
||||
|
||||
// === УНИКАЛЬНЫЙ SCOPE ID ДЛЯ ЭТОЙ НОДЫ ===
|
||||
const scopeId = `rs-upscaler-${this.id}`;
|
||||
|
||||
// Инжектируем скоупированные стили один раз для этого ID
|
||||
if (!document.getElementById(`style-${scopeId}`)) {
|
||||
const style = document.createElement("style");
|
||||
style.id = `style-${scopeId}`;
|
||||
style.textContent = getScopedStyles(scopeId);
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// === РАЗМЕР НОДЫ ===
|
||||
this.setSize([NODE_WIDTH, NODE_HEIGHT]);
|
||||
|
||||
this.onResize = function (size) {
|
||||
if (size[0] < NODE_WIDTH) size[0] = NODE_WIDTH;
|
||||
if (size[1] < NODE_HEIGHT) size[1] = NODE_HEIGHT;
|
||||
};
|
||||
|
||||
// === АГРЕССИВНОЕ СКРЫТИЕ НАТИВНЫХ ВИДЖЕТОВ ===
|
||||
const widgetsToHide = ["upscale_model", "upscale_method", "upscale_x"];
|
||||
this.widgets.forEach((w) => {
|
||||
if (widgetsToHide.includes(w.name)) {
|
||||
w.hidden = true;
|
||||
w.tooltip = "";
|
||||
w.type = "hidden";
|
||||
if (w.element) {
|
||||
w.element.style.display = "none";
|
||||
w.element.style.pointerEvents = "none";
|
||||
}
|
||||
w.computeSize = () => [0, 0];
|
||||
}
|
||||
});
|
||||
|
||||
// === КОНТЕЙНЕР С УНИКАЛЬНЫМ SCOPE ===
|
||||
const container = document.createElement("div");
|
||||
container.className = scopeId;
|
||||
|
||||
// --- Строка 1: Model ---
|
||||
const row1 = document.createElement("div");
|
||||
row1.className = "rs-row";
|
||||
const modelLabel = document.createElement("label");
|
||||
modelLabel.textContent = "Model";
|
||||
row1.appendChild(modelLabel);
|
||||
const modelSelect = document.createElement("select");
|
||||
row1.appendChild(modelSelect);
|
||||
container.appendChild(row1);
|
||||
|
||||
fetchUpscaleModels().then((models) => {
|
||||
models.forEach((m) => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = m;
|
||||
opt.textContent = m.replace(/\.(pth|bin|pt)$/i, "");
|
||||
modelSelect.appendChild(opt);
|
||||
});
|
||||
const currentModel = this.widgets.find((w) => w.name === "upscale_model")?.value;
|
||||
if (currentModel) modelSelect.value = currentModel;
|
||||
});
|
||||
|
||||
// --- Строка 2: Method ---
|
||||
const row2 = document.createElement("div");
|
||||
row2.className = "rs-row";
|
||||
const methodLabel = document.createElement("label");
|
||||
methodLabel.textContent = "Method";
|
||||
row2.appendChild(methodLabel);
|
||||
const methodSelect = document.createElement("select");
|
||||
const methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"];
|
||||
methods.forEach((m) => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = m;
|
||||
opt.textContent = m.charAt(0).toUpperCase() + m.slice(1);
|
||||
methodSelect.appendChild(opt);
|
||||
});
|
||||
row2.appendChild(methodSelect);
|
||||
container.appendChild(row2);
|
||||
|
||||
// --- Строка 3: [Label][Slider][◀][Value][▶] ---
|
||||
const row3 = document.createElement("div");
|
||||
row3.className = "rs-slider-row";
|
||||
|
||||
const sliderLabel = document.createElement("label");
|
||||
sliderLabel.textContent = "Scale";
|
||||
row3.appendChild(sliderLabel);
|
||||
|
||||
const slider = document.createElement("input");
|
||||
slider.type = "range";
|
||||
slider.min = String(SCALE_MIN);
|
||||
slider.max = String(SCALE_MAX);
|
||||
slider.step = String(SCALE_STEP);
|
||||
slider.value = String(DEFAULT_SCALE);
|
||||
row3.appendChild(slider);
|
||||
|
||||
const minusBtn = document.createElement("button");
|
||||
minusBtn.className = "rs-step-btn";
|
||||
minusBtn.textContent = "◀";
|
||||
minusBtn.title = "Decrease by " + SCALE_STEP;
|
||||
row3.appendChild(minusBtn);
|
||||
|
||||
const valueDisplay = document.createElement("div");
|
||||
valueDisplay.className = "rs-value-input";
|
||||
valueDisplay.textContent = formatScale(DEFAULT_SCALE);
|
||||
row3.appendChild(valueDisplay);
|
||||
|
||||
const plusBtn = document.createElement("button");
|
||||
plusBtn.className = "rs-step-btn";
|
||||
plusBtn.textContent = "▶";
|
||||
plusBtn.title = "Increase by " + SCALE_STEP;
|
||||
row3.appendChild(plusBtn);
|
||||
|
||||
container.appendChild(row3);
|
||||
|
||||
// === ОБНОВЛЕНИЕ ЗЕЛЁНОГО FILL ===
|
||||
const updateFill = () => {
|
||||
const pct = ((parseFloat(slider.value) - SCALE_MIN) / (SCALE_MAX - SCALE_MIN)) * 100;
|
||||
slider.style.setProperty("--rs-fill", pct + "%");
|
||||
};
|
||||
|
||||
// === ОБНОВЛЕНИЕ ЗНАЧЕНИЙ ===
|
||||
const setValue = (rawVal) => {
|
||||
const v = clampScale(Math.round(parseFloat(rawVal) / SCALE_STEP) * SCALE_STEP);
|
||||
slider.value = String(v);
|
||||
valueDisplay.textContent = formatScale(v);
|
||||
updateFill();
|
||||
syncToNative();
|
||||
};
|
||||
|
||||
const updateFromSlider = () => {
|
||||
valueDisplay.textContent = formatScale(slider.value);
|
||||
updateFill();
|
||||
syncToNative();
|
||||
};
|
||||
|
||||
slider.addEventListener("input", updateFromSlider);
|
||||
slider.addEventListener("change", updateFromSlider);
|
||||
|
||||
minusBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
setValue(parseFloat(slider.value) - SCALE_STEP);
|
||||
});
|
||||
|
||||
plusBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
setValue(parseFloat(slider.value) + SCALE_STEP);
|
||||
});
|
||||
|
||||
// === РУЧНОЙ ВВОД ПО КЛИКУ НА VALUE ===
|
||||
valueDisplay.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const existingPopup = document.querySelector(".rs-value-popup");
|
||||
if (existingPopup) existingPopup.remove();
|
||||
|
||||
const popup = document.createElement("div");
|
||||
popup.className = "rs-value-popup";
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.type = "number";
|
||||
input.value = formatScale(slider.value);
|
||||
input.min = String(SCALE_MIN);
|
||||
input.max = String(SCALE_MAX);
|
||||
input.step = String(SCALE_STEP);
|
||||
|
||||
const saveBtn = document.createElement("button");
|
||||
saveBtn.textContent = "OK";
|
||||
|
||||
const doSave = () => {
|
||||
let num = parseFloat(input.value);
|
||||
if (isNaN(num)) num = DEFAULT_SCALE;
|
||||
setValue(num);
|
||||
popup.remove();
|
||||
};
|
||||
|
||||
saveBtn.onclick = (ev) => { ev.stopPropagation(); ev.preventDefault(); doSave(); };
|
||||
input.onkeydown = (ev) => { if (ev.key === "Enter") { ev.preventDefault(); doSave(); } };
|
||||
|
||||
popup.appendChild(input);
|
||||
popup.appendChild(saveBtn);
|
||||
document.body.appendChild(popup);
|
||||
|
||||
const rect = valueDisplay.getBoundingClientRect();
|
||||
const popupWidth = popup.offsetWidth || 160;
|
||||
const popupHeight = popup.offsetHeight || 40;
|
||||
let leftPos = rect.right + 8;
|
||||
let topPos = rect.top + (rect.height - popupHeight) / 2;
|
||||
|
||||
if (leftPos + popupWidth > window.innerWidth - 10) {
|
||||
leftPos = rect.left - popupWidth - 8;
|
||||
}
|
||||
if (topPos < 10) topPos = 10;
|
||||
if (topPos + popupHeight > window.innerHeight - 10) {
|
||||
topPos = window.innerHeight - popupHeight - 10;
|
||||
}
|
||||
|
||||
popup.style.left = leftPos + "px";
|
||||
popup.style.top = topPos + "px";
|
||||
|
||||
setTimeout(() => { input.focus(); input.select(); }, 50);
|
||||
|
||||
setTimeout(() => {
|
||||
const closeHandler = (ev) => {
|
||||
if (!popup.contains(ev.target)) {
|
||||
popup.remove();
|
||||
document.removeEventListener("mousedown", closeHandler);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", closeHandler);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// === DOM-ВИДЖЕТ ===
|
||||
this.addDOMWidget("rs_custom_widgets", "div", container);
|
||||
|
||||
// === СИНХРОНИЗАЦИЯ ===
|
||||
const syncToNative = () => {
|
||||
const mw = this.widgets.find((w) => w.name === "upscale_model");
|
||||
const mtw = this.widgets.find((w) => w.name === "upscale_method");
|
||||
const xw = this.widgets.find((w) => w.name === "upscale_x");
|
||||
if (mw) mw.value = modelSelect.value;
|
||||
if (mtw) mtw.value = methodSelect.value;
|
||||
if (xw) xw.value = parseFloat(slider.value);
|
||||
this.graph?.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
modelSelect.addEventListener("change", syncToNative);
|
||||
methodSelect.addEventListener("change", syncToNative);
|
||||
|
||||
setTimeout(() => {
|
||||
syncToNative();
|
||||
updateFill();
|
||||
}, 100);
|
||||
|
||||
// === ВОССТАНОВЛЕНИЕ ПРИ ЗАГРУЗКЕ WORKFLOW ===
|
||||
const onConfigure = this.onConfigure;
|
||||
this.onConfigure = function (info) {
|
||||
const r = onConfigure?.apply(this, arguments);
|
||||
setTimeout(() => {
|
||||
const mw = this.widgets.find((w) => w.name === "upscale_model");
|
||||
const mtw = this.widgets.find((w) => w.name === "upscale_method");
|
||||
const xw = this.widgets.find((w) => w.name === "upscale_x");
|
||||
if (mw?.value) modelSelect.value = mw.value;
|
||||
if (mtw?.value) methodSelect.value = mtw.value;
|
||||
if (xw?.value != null) {
|
||||
const v = clampScale(parseFloat(xw.value));
|
||||
slider.value = String(v);
|
||||
valueDisplay.textContent = formatScale(v);
|
||||
} else {
|
||||
slider.value = String(DEFAULT_SCALE);
|
||||
valueDisplay.textContent = formatScale(DEFAULT_SCALE);
|
||||
}
|
||||
updateFill();
|
||||
syncToNative();
|
||||
}, 50);
|
||||
return r;
|
||||
};
|
||||
|
||||
// === ОЧИСТКА СТИЛЕЙ ПРИ УДАЛЕНИИ НОДЫ ===
|
||||
const onRemoved = this.onRemoved;
|
||||
this.onRemoved = function () {
|
||||
const styleEl = document.getElementById(`style-${scopeId}`);
|
||||
if (styleEl) styleEl.remove();
|
||||
if (onRemoved) onRemoved.apply(this, arguments);
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user