Compare commits

...
12 Commits
Author SHA1 Message Date
Rayko 10549d2f2c Update pyproject.toml 2026-11-11 07:14:04 +03:00
Rayko e929c122dc Update package.json 2026-11-11 07:13:48 +03:00
Rayko c7552847e8 Update __init__.py 2026-11-11 07:13:34 +03:00
Raykosan 4c906f42e4 Added the workflow manager to the RS Settings Manager extension 2026-11-11 07:12:40 +03:00
Raykosan a3769fc6a8 Added the workflow manager to the RS Settings Manager extension 2026-11-10 20:10:51 +03:00
Rayko cf04e9ea80 Update README.md 2026-11-10 20:08:51 +03:00
Rayko 45a83c260a Update pyproject.toml 2026-11-09 20:15:42 +03:00
Rayko 567a99e601 Update package.json 2026-11-09 20:15:28 +03:00
Rayko 2e67398dc7 Update __init__.py 2026-11-09 20:15:08 +03:00
Rayko e8c07160fa Update spline_mask.js 2026-11-09 20:14:27 +03:00
Rayko b9d09c4ec6 Update Rayko_Spline_Mask.py 2026-11-09 20:13:29 +03:00
Rayko 0dd7df8f65 Update README.md 2026-11-09 20:12:00 +03:00
9 changed files with 1442 additions and 448 deletions
+1
View File
@@ -6,6 +6,7 @@ __pycache__/
preset_colors/ preset_colors/
preset_models/ preset_models/
presets/ presets/
presets_workflow
prompts/ prompts/
rayko_lora_data/ rayko_lora_data/
styles/ styles/
+70 -49
View File
@@ -1192,7 +1192,7 @@ Detailed inspection of image differences at various zoom levels
# 🦊 RS Load Image # 🦊 RS Load Image
**A node for load image and creating a spline mask** **A node for load image and creating a spline mask**
<img width="800" height="715" alt="RS Load Image1" src="https://github.com/user-attachments/assets/c5150b6a-88b6-4e2a-86fa-7cd70346cab8" /> <img width="1041" height="747" alt="Screenshot_4" src="https://github.com/user-attachments/assets/35f3b53d-d569-443b-b391-5a334625165d" />
### 🔥 Features ### 🔥 Features
The node has the functionality of the native Load Image node, but instead of the native mask editor, a built-in spline editor is used. More accurate selection based on the principle of the Lasso tool from Photoshop. The node has the functionality of the native Load Image node, but instead of the native mask editor, a built-in spline editor is used. More accurate selection based on the principle of the Lasso tool from Photoshop.
@@ -1201,7 +1201,7 @@ The node has the functionality of the native Load Image node, but instead of the
The node is ready for use immediately after it is added. Images are added using the "🎨 IMAGE" (images from the input folder) and "🖼️ UPLOAD IMAGE" (images from any folder on your PC) buttons. You can scale the node to a convenient size to more accurately place the points of the spline. Incorrectly positioned points can be deleted by left-clicking with the CTRL key held down. To remove all points from the preview area, click the "🔴 CLEAR POINTS" button. The node is ready for use immediately after it is added. Images are added using the "🎨 IMAGE" (images from the input folder) and "🖼️ UPLOAD IMAGE" (images from any folder on your PC) buttons. You can scale the node to a convenient size to more accurately place the points of the spline. Incorrectly positioned points can be deleted by left-clicking with the CTRL key held down. To remove all points from the preview area, click the "🔴 CLEAR POINTS" button.
### ↔️ Inputs and Outputs: ### ↔️ Inputs and Outputs:
The IMAGE output returns the original image unchanged. The MASK output returns a black and white mask where the white area corresponds to the drawn polygon. The IMAGE output returns the original image unchanged. The MASK output returns a black and white mask where the white area corresponds to the drawn polygon. The WIDTH and HEIGHT output the dimensions of the uploaded image in INT.
</details> </details>
<details> <details>
@@ -2137,68 +2137,89 @@ The node generates a list of INT values, automatically switching between 10 pres
<details> <details>
<summary>🦊 ComfyUI Settings Manager</summary> <summary>🦊 ComfyUI Settings Manager</summary>
# 🦊 ComfyUI Settings Manager # 🦊 ComfyUI RaykoStudio: Settings & Workflow Preset Manager
**A user-friendly extension that allows you to easily backup, restore, and manage your interface settings. Never lose your custom UI layout, preferences, or configurations again!** **A user-friendly extension that allows you to easily manage your interface settings **and** group multiple workflows into "Presets". Never lose your custom UI layout or waste time opening the same 5 workflows again!**
<img width="217" height="937" alt="Screenshot_1" src="https://github.com/user-attachments/assets/a898ce80-dd21-4245-9228-d8b78ede8cf4" /> <img width="217" height="937" alt="Screenshot_1" src="https://github.com/user-attachments/assets/a898ce80-dd21-4245-9228-d8b78ede8cf4" />
<img width="207" height="136" alt="Screenshot_4" src="https://github.com/user-attachments/assets/13ad352f-295e-4e5e-9fdb-2b55d7f80c37" /> <img width="239" height="86" alt="Screenshot_1" src="https://github.com/user-attachments/assets/e1829c76-b739-4a07-8ade-3bad3117efbc" />
<img width="580" height="783" alt="Screenshot_3" src="https://github.com/user-attachments/assets/f139e9c5-64d5-44d2-9790-bb266f8bd078" /> <img width="506" height="814" alt="Screenshot_2" src="https://github.com/user-attachments/assets/1dbd5cb1-3a85-43c3-a191-46b9e5cb92d0" />
<img width="438" height="54" alt="Screenshot_5" src="https://github.com/user-attachments/assets/ccc1a109-e7bf-4ad1-a537-ffe6b0c3cb33" /> <img width="370" height="186" alt="Screenshot_3" src="https://github.com/user-attachments/assets/88b9bb97-6ee2-4828-a776-c51ab3a9be5f" />
### 🔥 Features ## 🔥 Features
- **Smart Backups** - Save your current interface settings with custom names. If no name is provided, it automatically uses a timestamp.
- **Easy Restore** - Select any previous backup from a clean UI list and restore it in one click.
- **Built-in Server Restart** - Restoring settings requires a server restart. The extension provides a dedicated, pulsing **RESTART SERVER** button to make this obvious and safe.
- **Auto-Refresh Polling** - After restarting, the extension automatically pings the server and refreshes your browser page the moment ComfyUI is back online.
- **Manage Backups** - Delete old or unnecessary backups directly from the interface without leaving ComfyUI.
- **Safe Naming** - Automatically sanitizes custom backup names (removes invalid characters) and prevents overwriting by adding suffixes (`_1`, `_2`) if a name collision occurs.
- You can use common settings for multiple versions or assemblies of ComfyUI.
- You can create several interface configurations and change them with a couple of clicks, rather than crawling through all the settings tabs.
- It's also useful for those who mess up the interface settings a lot - you can always roll back to the previous version.
### 🪛 Usage ### 📂 Workflow Presets
**Accessing the Manager** * **Save Workflow Sets:** Save all currently open workflow tabs (e.g., *Base Workflow, Upscaler, Face Detailer*) as a single preset.
Open the **Settings** (gear icon on the sidebar) * **Active Preset Tracking:** The extension remembers which preset you are currently working in.
Open the menu item **RaykoStudio** * **Smart Save & Close:** Before closing all tabs or loading a new preset, the extension asks if you want to save your recent changes back to the active preset.
You can enable/disable the display of the extension icon in the ComfyUI top menu [🖥️] * **Instant Update:** Added a new node to a workflow? Just click "Update This Preset" to instantly overwrite the preset file.
Click **Open Setting Manager** * **Safe "Close All":** Closes all open tabs safely without breaking ComfyUI's internal state, leaving you with a clean workspace.
* **Smart Filtering:** Automatically ignores empty "Unsaved Workflow" tabs when saving, keeping your presets clean.
**Saving Settings (Backup)** ### ⚙️ Interface Settings Backups
1. In the **Save Interface Settings** section, you will see a text field. * **Smart Backups:** Save your current `comfy.settings.json` with custom names. If no name is provided, it automatically uses a timestamp.
2. *(Optional)* Enter a custom name for your backup (e.g., `my_favorite_layout`). * **Easy Restore:** Select any previous backup from a clean UI list and restore it in one click.
- *Note: If you leave it empty, the current date and time will be used.* * **Built-in Server Restart:** The extension provides a dedicated, pulsing `RESTART SERVER` button to make the process obvious and safe.
3. Click the **💾 Save Settings** button. * **Auto-Refresh Polling:** After restarting, the extension automatically pings the server and refreshes your browser the moment ComfyUI is back online.
4. A green status message will appear at the bottom confirming the save. If the list of backups is open, the new backup will instantly appear at the top. * **Manage Backups:** Delete old or unnecessary backups directly from the interface.
* **Safe Naming:** Automatically sanitizes custom backup names and prevents overwriting by adding suffixes (`_1`, `_2`) if a name collision occurs.
**Restoring Settings** ## 🪛 Usage
### Accessing the Manager
Since the native ComfyUI settings window often conflicts with custom modals, this extension uses the Topbar:
1. Go to ComfyUI Settings (`⚙️` icon on the sidebar).
2. Scroll down to the **RaykoStudio** section.
3. Check the **"Show icon in top menu"** box.
4. Look at the top menu (next to the `RUN` button). You will see two new buttons:
* 🖥️ **Settings Manager** — Opens the main interface.
* 🗑️ **Close All Workflows** — Instantly closes all open tabs (with a confirmation dialog).
### 🗂️ Workflow Presets
1. Open the Settings Manager (🖥️) and click **📂 Show Presets**.
2. Open the workflow tabs you want to group together.
3. Type a name (e.g., "Anime Pipeline") in the preset field and click **💾 Save Open Tabs as New Preset**.
4. A green banner will appear at the top of the manager: *Active Preset: "Anime Pipeline"*.
**To update a preset:**
If you make changes or add new workflows, just click **💾 Update This Preset** in the green banner.
**To load a preset:**
Click **Load** next to any preset. If you have unsaved changes in your current session, a dialog will appear asking to **Save, Discard, or Cancel**.
### 💾 Saving Interface Settings (Backup)
1. In the **Save Interface Settings** section, enter a custom name for your backup (e.g., `my_favorite_layout`).
*(Note: If you leave it empty, the current date and time will be used).*
2. Click **💾 Save Settings**. A green status message will confirm the save.
### 🔄 Restoring Interface Settings
1. In the **Restore Interface Settings** section, click **📂 Load Backups**. 1. In the **Restore Interface Settings** section, click **📂 Load Backups**.
2. A list of your saved backups will appear. 2. Click **✓ Restore** next to the backup you want to load.
3. Click **✓ Restore** next to the backup you want to load. 3. A pulsing orange **🔄 RESTART SERVER** button will appear. Click it.
4. The button will change to **✓ Restored**, and a new, pulsing orange button **🔄 RESTART SERVER** will appear. 4. The extension will wait for the server to come back online and automatically refresh your browser page.
5. Click **🔄 RESTART SERVER**.
6. The extension will automatically wait for the server to come back online and **refresh your browser page** automatically.
**Deleting Backups** ## 📁 File Structure
1. Load your backups list.
2. Click the red **✕** button next to any backup you want to remove.
3. The backup is instantly deleted from the UI and the file system.
### 📁 File Structure **Workflow Presets** are saved directly inside the extension folder so they can be easily shared:
```text
All backups are stored outside the ComfyUI directory to keep your installation clean. By default, they are saved in your user Documents folder: 📂 ComfyUI/custom_nodes/ComfyUI_RaykoStudio/
└── 📂 presets_workflow/
├── 📄 Anime Pipeline.json
└── 📄 Technical Nodes.json
```
**Interface Backups** are stored outside the ComfyUI directory to keep your installation clean (in your user Documents folder):
```text ```text
📂 Documents/ 📂 Documents/
── 📂 ComfyUI_Settings_Backups/ └── 📂 ComfyUI_Settings_Backups/
├── 📂 2026-06-09_12-40-44/ ├── 📂 2026-06-09_12-40-44/
│ ── 📄 comfy.settings.json
├── 📂 my_favorite_layout/
│ └── 📄 comfy.settings.json │ └── 📄 comfy.settings.json
── ... └── 📂 my_favorite_layout/
└── 📄 comfy.settings.json
``` ```
### ⚙️ Technical Notes ## ⚙️ Technical Notes
- **Server Restart** - The extension uses `os.execv` to restart the Python server. If you are using a third-party launcher (like Stability Matrix or Pinokio), the server might close but not automatically reopen. In this case, simply restart your launcher manually. * **Active Preset Tracking:** The extension uses `sessionStorage` to remember your active preset. Refreshing the page keeps the state, but closing the browser resets it.
- **Polling** - After a restart, the extension pings the server every 5 seconds (up to 10 times) to detect when it's fully loaded before triggering the page reload. * **Server Restart:** The extension uses `os.execv` to restart the Python server. If you are using a third-party launcher (like Stability Matrix or Pinokio), the server might close but not automatically reopen. In this case, simply restart your launcher manually.
* **Polling:** After a restart, the extension pings the server every 5 seconds (up to 10 times) to detect when it's fully loaded before triggering the page reload.
</details> </details>
+9 -5
View File
@@ -39,11 +39,10 @@ class RaykoSplineMask:
} }
return inputs return inputs
RETURN_TYPES = ("IMAGE", "MASK") RETURN_TYPES = ("IMAGE", "MASK", "INT", "INT")
RETURN_NAMES = ("image", "mask") RETURN_NAMES = ("image", "mask", "width", "height")
FUNCTION = "create_mask" FUNCTION = "create_mask"
CATEGORY = "🦊 RaykoStudio" CATEGORY = "🦊 RaykoStudio"
OUTPUT_NODE = True
DESCRIPTION = "A node for load image and creating a spline mask" DESCRIPTION = "A node for load image and creating a spline mask"
def create_mask(self, image, coordinates="[]"): def create_mask(self, image, coordinates="[]"):
@@ -74,7 +73,12 @@ class RaykoSplineMask:
if img_tensor is None: if img_tensor is None:
h, w = 512, 512 h, w = 512, 512
return (torch.zeros((1, h, w, 3)), torch.zeros((1, h, w))) return (
torch.zeros((1, h, w, 3)),
torch.zeros((1, h, w)),
int(w),
int(h),
)
h, w = img_tensor.shape[1], img_tensor.shape[2] h, w = img_tensor.shape[1], img_tensor.shape[2]
@@ -99,7 +103,7 @@ class RaykoSplineMask:
print(f"[SPLINE 🦊] Error parsing coordinates: {e}") print(f"[SPLINE 🦊] Error parsing coordinates: {e}")
mask_tensor = torch.from_numpy(mask_np).unsqueeze(0) mask_tensor = torch.from_numpy(mask_np).unsqueeze(0)
return (img_tensor, mask_tensor) return (img_tensor, mask_tensor, int(w), int(h))
if PromptServer is not None and web is not None: if PromptServer is not None and web is not None:
@PromptServer.instance.routes.get("/rayko/spline/images") @PromptServer.instance.routes.get("/rayko/spline/images")
+1 -1
View File
@@ -29,7 +29,7 @@ IGNORED_MODULES: Set[str] = {'__init__.py'}
REQUIRED_MODULES: Set[str] = set() REQUIRED_MODULES: Set[str] = set()
PACKAGE_DISPLAY_NAME = "ComfyUI_RaykoStudio" PACKAGE_DISPLAY_NAME = "ComfyUI_RaykoStudio"
__version__ = "0.51.3" __version__ = "0.52.1"
def load_modules(): def load_modules():
print(f"🦊 {PACKAGE_DISPLAY_NAME}: Version: {__version__}") print(f"🦊 {PACKAGE_DISPLAY_NAME}: Version: {__version__}")
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ComfyUI_RaykoStudio", "name": "ComfyUI_RaykoStudio",
"version": "0.51.3", "version": "0.52.1",
"description": "Set of custom nodes for ComfyUI providing additional image processing capabilities", "description": "Set of custom nodes for ComfyUI providing additional image processing capabilities",
"author": "Raykosan", "author": "Raykosan",
"license": "Apache License 2.0", "license": "Apache License 2.0",
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "ComfyUI_RaykoStudio" name = "ComfyUI_RaykoStudio"
description = "Set of custom nodes for ComfyUI providing additional image processing capabilities" description = "Set of custom nodes for ComfyUI providing additional image processing capabilities"
version = "0.51.3" version = "0.52.1"
license = { file = "LICENSE" } license = { file = "LICENSE" }
dependencies = ["torch", "opencv-python", "numpy", "Pillow>=10.0.0", "freetype-py>=2.5.1", "pycairo>=1.29.0", "scipy>=1.10.0"] dependencies = ["torch", "opencv-python", "numpy", "Pillow>=10.0.0", "freetype-py>=2.5.1", "pycairo>=1.29.0", "scipy>=1.10.0"]
+94
View File
@@ -15,6 +15,7 @@
import os import os
import sys import sys
import json
import shutil import shutil
import datetime import datetime
import logging import logging
@@ -28,14 +29,21 @@ CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
COMFYUI_ROOT = os.path.dirname(os.path.dirname(CURRENT_DIR)) COMFYUI_ROOT = os.path.dirname(os.path.dirname(CURRENT_DIR))
SETTINGS_FILE = os.path.join(COMFYUI_ROOT, "user", "default", "comfy.settings.json") SETTINGS_FILE = os.path.join(COMFYUI_ROOT, "user", "default", "comfy.settings.json")
BACKUP_DIR = os.path.join(os.path.expanduser("~"), "Documents", "ComfyUI_Settings_Backups") BACKUP_DIR = os.path.join(os.path.expanduser("~"), "Documents", "ComfyUI_Settings_Backups")
PRESETS_DIR = os.path.join(CURRENT_DIR, "presets_workflow")
os.makedirs(BACKUP_DIR, exist_ok=True) os.makedirs(BACKUP_DIR, exist_ok=True)
os.makedirs(PRESETS_DIR, exist_ok=True)
logger.info("🦊 Settings Manager initialized") logger.info("🦊 Settings Manager initialized")
logger.info(f"🦊 Presets folder: {PRESETS_DIR}")
# -------------------- Settings Backups --------------------
@PromptServer.instance.routes.get("/rayko_settings_manager/ping") @PromptServer.instance.routes.get("/rayko_settings_manager/ping")
async def ping(request): async def ping(request):
return web.json_response({"status": "ok"}) return web.json_response({"status": "ok"})
@PromptServer.instance.routes.post("/rayko_settings_manager/save") @PromptServer.instance.routes.post("/rayko_settings_manager/save")
async def save_settings(request): async def save_settings(request):
try: try:
@@ -67,6 +75,7 @@ async def save_settings(request):
except Exception as e: except Exception as e:
return web.json_response({"error": str(e)}, status=500) return web.json_response({"error": str(e)}, status=500)
@PromptServer.instance.routes.get("/rayko_settings_manager/list") @PromptServer.instance.routes.get("/rayko_settings_manager/list")
async def list_settings(request): async def list_settings(request):
try: try:
@@ -79,6 +88,7 @@ async def list_settings(request):
except Exception as e: except Exception as e:
return web.json_response({"error": str(e)}, status=500) return web.json_response({"error": str(e)}, status=500)
@PromptServer.instance.routes.post("/rayko_settings_manager/restore") @PromptServer.instance.routes.post("/rayko_settings_manager/restore")
async def restore_settings(request): async def restore_settings(request):
try: try:
@@ -98,6 +108,7 @@ async def restore_settings(request):
except Exception as e: except Exception as e:
return web.json_response({"error": str(e)}, status=500) return web.json_response({"error": str(e)}, status=500)
@PromptServer.instance.routes.delete("/rayko_settings_manager/delete/{backup_name}") @PromptServer.instance.routes.delete("/rayko_settings_manager/delete/{backup_name}")
async def delete_backup(request): async def delete_backup(request):
try: try:
@@ -116,6 +127,7 @@ async def delete_backup(request):
except Exception as e: except Exception as e:
return web.json_response({"error": str(e)}, status=500) return web.json_response({"error": str(e)}, status=500)
@PromptServer.instance.routes.post("/rayko_settings_manager/restart") @PromptServer.instance.routes.post("/rayko_settings_manager/restart")
async def restart_server(request): async def restart_server(request):
try: try:
@@ -124,5 +136,87 @@ async def restart_server(request):
except Exception as e: except Exception as e:
return web.json_response({"error": str(e)}, status=500) return web.json_response({"error": str(e)}, status=500)
# -------------------- Workflow Presets --------------------
def _sanitize_name(name: str) -> str:
return re.sub(r'[<>:"/\\|?*]', '_', (name or "").strip()).rstrip('. ')
@PromptServer.instance.routes.get("/rayko_settings_manager/presets/list")
async def list_presets(request):
try:
presets = []
if os.path.exists(PRESETS_DIR):
for f in sorted(os.listdir(PRESETS_DIR)):
if not f.endswith(".json"):
continue
try:
with open(os.path.join(PRESETS_DIR, f), "r", encoding="utf-8") as fp:
data = json.load(fp)
presets.append({
"name": f[:-5],
"count": len(data.get("workflows", [])),
})
except Exception as e:
logger.warning(f"Failed to read preset {f}: {e}")
return web.json_response({"success": True, "presets": presets})
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@PromptServer.instance.routes.get("/rayko_settings_manager/presets/get/{name}")
async def get_preset(request):
try:
name = _sanitize_name(request.match_info.get("name") or "")
if not name:
return web.json_response({"error": "Name required"}, status=400)
path = os.path.join(PRESETS_DIR, f"{name}.json")
if not os.path.exists(path):
return web.json_response({"error": "Not found"}, status=404)
with open(path, "r", encoding="utf-8") as f:
return web.json_response(json.load(f))
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@PromptServer.instance.routes.post("/rayko_settings_manager/presets/save")
async def save_preset(request):
try:
data = await request.json()
name = _sanitize_name(data.get("name", ""))
workflows = data.get("workflows", [])
if not name:
return web.json_response({"error": "Name required"}, status=400)
if not workflows:
return web.json_response({"error": "No workflows to save"}, status=400)
payload = {
"created": datetime.datetime.now().isoformat(timespec="seconds"),
"workflows": workflows,
}
path = os.path.join(PRESETS_DIR, f"{name}.json")
with open(path, "w", encoding="utf-8") as f:
json.dump(payload, f, ensure_ascii=False, indent=2)
return web.json_response({"success": True, "name": name, "count": len(workflows)})
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@PromptServer.instance.routes.delete("/rayko_settings_manager/presets/delete/{name}")
async def delete_preset(request):
try:
name = _sanitize_name(request.match_info.get("name") or "")
if not name:
return web.json_response({"error": "Name required"}, status=400)
path = os.path.join(PRESETS_DIR, f"{name}.json")
if not os.path.exists(path):
return web.json_response({"error": "Not found"}, status=404)
os.remove(path)
return web.json_response({"success": True})
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
NODE_CLASS_MAPPINGS = {} NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {} NODE_DISPLAY_NAME_MAPPINGS = {}
+1195 -322
View File
File diff suppressed because it is too large Load Diff
+12 -11
View File
@@ -17,8 +17,9 @@ app.registerExtension({
spline_coords: "[]" spline_coords: "[]"
}; };
// Чуть увеличиваем высоту, чтобы вместить 4 слота + виджеты + кнопки
node.targetWidth = 320; node.targetWidth = 320;
node.setSize([320, 410]); node.setSize([320, 440]);
const imageWidget = node.widgets?.find(w => w.name === "image"); const imageWidget = node.widgets?.find(w => w.name === "image");
const coordsWidget = node.widgets?.find(w => w.name === "coordinates"); const coordsWidget = node.widgets?.find(w => w.name === "coordinates");
@@ -48,7 +49,6 @@ app.registerExtension({
node.buttons = []; node.buttons = [];
node.imageList = []; node.imageList = [];
// Новая функция для создания полной маски
node.fillFullMask = function() { node.fillFullMask = function() {
if (!node.imageLoaded || !node.image) return; if (!node.imageLoaded || !node.image) return;
@@ -494,7 +494,7 @@ app.registerExtension({
const padding = 15; const padding = 15;
const gap = 10; const gap = 10;
const btnW = (this.size[0] - 2 * padding - 3 * gap) / 4; // Пересчет на 4 кнопки const btnW = (this.size[0] - 2 * padding - 3 * gap) / 4;
const btnH = 28; const btnH = 28;
const btnYOffset = 45; const btnYOffset = 45;
@@ -539,18 +539,21 @@ app.registerExtension({
const nodeScreenX = canvasRect.left + ((graphX + ds.offset[0]) * scale); const nodeScreenX = canvasRect.left + ((graphX + ds.offset[0]) * scale);
const nodeScreenY = canvasRect.top + ((graphY + ds.offset[1]) * scale); const nodeScreenY = canvasRect.top + ((graphY + ds.offset[1]) * scale);
// Считаем высоту абсолютно всех виджетов (даже скрытых)
let widgetsHeight = 0; let widgetsHeight = 0;
if (node.widgets) { if (node.widgets) {
for (const w of node.widgets) { for (const w of node.widgets) {
if (!w.hidden) widgetsHeight += 20; widgetsHeight += 20;
} }
} }
const titleBarHeight = LiteGraph.NODE_TITLE_HEIGHT || 30; const titleBarHeight = LiteGraph.NODE_TITLE_HEIGHT || 30;
// Оставляем стандартные отступы, чтобы картинка занимала всю ширину
const leftRightPadding = 10; const leftRightPadding = 10;
const topPadding = 18; const topPadding = 18;
const bottomPadding = 10; const bottomPadding = 10;
const footerHeight = 50; const footerHeight = 60; // Выделяем место под кнопки в самом низу
const availableHeight = (node.size[1] * scale) - (titleBarHeight * scale) - (widgetsHeight * scale) - (footerHeight * scale) - (topPadding * scale) - (bottomPadding * scale); const availableHeight = (node.size[1] * scale) - (titleBarHeight * scale) - (widgetsHeight * scale) - (footerHeight * scale) - (topPadding * scale) - (bottomPadding * scale);
const availableWidth = (node.size[0] * scale) - (leftRightPadding * 2 * scale); const availableWidth = (node.size[0] * scale) - (leftRightPadding * 2 * scale);
@@ -738,7 +741,7 @@ app.registerExtension({
let widgetsHeight = 0; let widgetsHeight = 0;
if (this.widgets) { if (this.widgets) {
for (const w of this.widgets) { for (const w of this.widgets) {
if (!w.hidden) widgetsHeight += 20; widgetsHeight += 20;
} }
} }
@@ -746,11 +749,10 @@ app.registerExtension({
const leftRightPadding = 10; const leftRightPadding = 10;
const topPadding = 18; const topPadding = 18;
const bottomPadding = 10; const bottomPadding = 10;
const footerHeight = 50; const footerHeight = 60; // Выделяем место под кнопки
const btnH = 28; const btnH = 28;
const btnY = h - 45; const btnY = h - 45;
// Пересчет ширины кнопок на 4 элемента
const btnPadding = 15; const btnPadding = 15;
const btnGap = 10; const btnGap = 10;
const btnW = (w - 2 * btnPadding - 3 * btnGap) / 4; const btnW = (w - 2 * btnPadding - 3 * btnGap) / 4;
@@ -809,10 +811,9 @@ app.registerExtension({
const radius = 4; const radius = 4;
const x = btn.x, y = btn.y, w = btn.w, h = btn.h; const x = btn.x, y = btn.y, w = btn.w, h = btn.h;
// Ховер для бледной кнопки FULL
let fillColor = btn.color; let fillColor = btn.color;
if (btn.label === "✔️ FULL" && btn.hover) { if (btn.label === "✔️ FULL" && btn.hover) {
fillColor = "#4CAF50"; // Полная яркость при наведении fillColor = "#4CAF50";
} }
ctx.fillStyle = btn.hover ? "#444" : "#2a2a2a"; ctx.fillStyle = btn.hover ? "#444" : "#2a2a2a";
@@ -867,7 +868,7 @@ app.registerExtension({
node.onResize = function(size) { node.onResize = function(size) {
if (size[0] < 320) size[0] = 320; if (size[0] < 320) size[0] = 320;
if (size[1] < 410) size[1] = 410; if (size[1] < 440) size[1] = 440;
_lastRect = null; _lastRect = null;
syncPosition(); syncPosition();