chore: exclude example folder from git
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
example/
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'bradsec' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
@@ -1,125 +0,0 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.env
|
||||
.build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.coverage
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage.*
|
||||
*.cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
Pipfile.lock
|
||||
|
||||
# poetry
|
||||
poetry.lock
|
||||
|
||||
# vscode
|
||||
.vscode/
|
||||
.settings/
|
||||
*.code-workspace
|
||||
|
||||
# Sublime Text
|
||||
*.sublime-workspace
|
||||
|
||||
# SFTP configuration
|
||||
.sftp-config.json
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# Pytest cache
|
||||
__pytest_cache__/
|
||||
pytestdebuglog.txt
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Claude Code files
|
||||
.claude/
|
||||
CLAUDE.md
|
||||
@@ -1,10 +0,0 @@
|
||||
.github/workflows/publish.yml
|
||||
.gitignore
|
||||
LICENSE
|
||||
README.md
|
||||
__init__.py
|
||||
node_preview.png
|
||||
pyproject.toml
|
||||
resolution_selector.py
|
||||
test_resolution_selector.py
|
||||
web/js/resolution_selector.js
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 BRADSEC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,74 +0,0 @@
|
||||
# Resolution Selector Plus for ComfyUI
|
||||
|
||||
Custom node for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) providing easy resolution and empty latent selection for image generation workflows.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
### Resolution Selector
|
||||
- **Model-optimised resolution presets** (portrait, landscape, square)
|
||||
- **Empty latent output** for direct KSampler connection
|
||||
- **Custom width/height inputs** with **independent multiplier and latent**
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Change to the directory you installed ComfyUI
|
||||
cd pathTo/ComfyUI
|
||||
|
||||
# Change to the custom_nodes directory
|
||||
cd custom_nodes
|
||||
|
||||
# Clone the repo into custom_nodes
|
||||
git clone https://github.com/bradsec/ComfyUI_ResolutionSelectorPlus.git
|
||||
|
||||
# Restart ComfyUI
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Resolution Selector Plus
|
||||
|
||||
**Add Node:** `Add Node > utils > Resolution Selector Plus`
|
||||
|
||||
**Inputs:**
|
||||
- `model` (dropdown) - Select your image generation model or "All" for all resolutions
|
||||
- `resolution` (dropdown) - Choose from model-optimized presets with aspect ratios (e.g., "1920x1080 (16:9 Landscape)")
|
||||
- `resolution_multiplier` (dropdown) - Multiply preset resolution (1x, 2x, 3x, 4x)
|
||||
- `batch_size` (number) - Number of latent samples to generate (1-64, default: 1)
|
||||
- `custom_width` (optional) - Override with custom width (0-4096, step 8)
|
||||
- `custom_height` (optional) - Override with custom height (0-4096, step 8)
|
||||
- `custom_multiplier` (optional) - Independent multiplier for custom dimensions (1x, 2x, 3x, 4x)
|
||||
- `custom_batch` (optional) - Number of latent samples for custom resolution (1-64, default: 1)
|
||||
|
||||
**Outputs:**
|
||||
- `width` (INT) - Preset resolution width in pixels
|
||||
- `height` (INT) - Preset resolution height in pixels
|
||||
- `latent` (LATENT) - Empty latent tensor for preset resolution (batch_size samples)
|
||||
- `custom_width` (INT) - Custom width (0 if not set)
|
||||
- `custom_height` (INT) - Custom height (0 if not set)
|
||||
- `custom_latent` (LATENT) - Empty latent tensor for custom resolution (custom_batch samples)
|
||||
|
||||
**Example Workflows:**
|
||||
|
||||
**Basic preset with multiplier:**
|
||||
```
|
||||
Resolution Selector Plus (model: SDXL, resolution: "1024x1024 (1:1 Square)", multiplier: 2x)
|
||||
→ width: 2048, height: 2048
|
||||
→ latent → KSampler
|
||||
```
|
||||
|
||||
**Custom dimensions with independent multiplier:**
|
||||
```
|
||||
Resolution Selector Plus (custom_width: 1024, custom_height: 768, custom_multiplier: 2x)
|
||||
→ custom_width: 2048, custom_height: 1536
|
||||
→ custom_latent → KSampler
|
||||
```
|
||||
|
||||
**Using "All" model option:**
|
||||
```
|
||||
Resolution Selector Plus (model: All, resolution: "1920x1080 (16:9 Landscape)")
|
||||
→ Shows all 74 unique resolutions from all models
|
||||
```
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
from .resolution_selector import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB |
@@ -1,14 +0,0 @@
|
||||
[project]
|
||||
name = "comfyui_resolutionselectorplus"
|
||||
description = "Custom node for ComfyUI providing easy resolution and empty latent selection for image generation workflows."
|
||||
version = "2.0.5"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/bradsec/ComfyUI_ResolutionSelectorPlus"
|
||||
# Used by Comfy Registry https://comfyregistry.org
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "bradsec"
|
||||
DisplayName = "ComfyUI_ResolutionSelectorPlus"
|
||||
Icon = ""
|
||||
@@ -1,447 +0,0 @@
|
||||
import torch
|
||||
|
||||
try:
|
||||
import comfy.model_management
|
||||
COMFY_AVAILABLE = True
|
||||
except ImportError:
|
||||
COMFY_AVAILABLE = False
|
||||
|
||||
|
||||
# Model-specific resolution presets with constraints
|
||||
# Includes: model-optimized sizes + photo print (4x6, 5x7, 8x10) + digital/social + canvas art ratios
|
||||
MODEL_RESOLUTIONS = {
|
||||
"Flux": {
|
||||
"square": [(512, 512), (768, 768), (1024, 1024), (1088, 1088), (1280, 1280), (1536, 1536), (1920, 1920), (2048, 2048)],
|
||||
"portrait": [(688, 2048), (768, 1344), (832, 1216), (896, 1152), (928, 1664), (1024, 1536), (1024, 1792), (1024, 2048), (1088, 1920), (1152, 2048), (1200, 1792), (1360, 2048), (1456, 2048), (1536, 2048), (1616, 2048), (1632, 2048), (1712, 2048)],
|
||||
"landscape": [(1280, 720), (1344, 768), (1216, 832), (1152, 896), (1536, 1024), (1664, 928), (1792, 1024), (1792, 1200), (1920, 1088), (2048, 688), (2048, 1024), (2048, 1152), (2048, 1360), (2048, 1456), (2048, 1536), (2048, 1616), (2048, 1632), (2048, 1712)],
|
||||
"constraints": {"divisible_by": 16, "min": 256, "max": 2048}
|
||||
},
|
||||
"Qwen Image": {
|
||||
"square": [(1024, 1024), (1080, 1080), (1280, 1280), (1328, 1328), (1536, 1536), (1920, 1920), (2048, 2048)],
|
||||
"portrait": [(680, 2048), (928, 1664), (1024, 1536), (1024, 2048), (1080, 1920), (1140, 1472), (1152, 2048), (1200, 1800), (1368, 2048), (1464, 2048), (1536, 2048), (1608, 2048), (1640, 2048), (1704, 2048)],
|
||||
"landscape": [(1280, 720), (1472, 1140), (1536, 1024), (1664, 928), (1800, 1200), (1920, 1080), (2048, 680), (2048, 1024), (2048, 1152), (2048, 1368), (2048, 1464), (2048, 1536), (2048, 1608), (2048, 1640), (2048, 1704)],
|
||||
"constraints": {"divisible_by": 8, "min": 256, "max": 2048}
|
||||
},
|
||||
"Z-Image": {
|
||||
"square": [(512, 512), (768, 768), (1024, 1024), (1080, 1080), (1280, 1280), (1536, 1536), (1920, 1920), (2048, 2048)],
|
||||
"portrait": [(680, 2048), (720, 1280), (768, 1024), (1024, 2048), (1080, 1920), (1152, 2048), (1200, 1800), (1368, 2048), (1464, 2048), (1536, 2048), (1608, 2048), (1640, 2048), (1704, 2048)],
|
||||
"landscape": [(1024, 768), (1280, 720), (1800, 1200), (1920, 1080), (2048, 680), (2048, 1024), (2048, 1152), (2048, 1368), (2048, 1464), (2048, 1536), (2048, 1608), (2048, 1640), (2048, 1704)],
|
||||
"constraints": {"divisible_by": 8, "min": 256, "max": 2048}
|
||||
},
|
||||
"SD 1.5": {
|
||||
"square": [(512, 512), (768, 768), (1024, 1024), (1080, 1080), (1280, 1280), (1536, 1536)],
|
||||
"portrait": [(512, 768), (512, 682), (512, 1024), (680, 2048), (768, 1024), (768, 1344), (1024, 2048), (1080, 1920), (1200, 1800), (1368, 2048), (1464, 2048), (1536, 2048), (1608, 2048), (1640, 2048), (1704, 2048)],
|
||||
"landscape": [(768, 512), (1024, 512), (1024, 768), (1280, 720), (1344, 768), (1536, 512), (1800, 1200), (1920, 1080), (2048, 680), (2048, 1024), (2048, 1368), (2048, 1464), (2048, 1536), (2048, 1608), (2048, 1640), (2048, 1704)],
|
||||
"constraints": {"divisible_by": 8, "min": 256, "max": 2048}
|
||||
},
|
||||
"SDXL": {
|
||||
"square": [(1024, 1024), (1080, 1080), (1280, 1280), (1536, 1536), (1920, 1920), (2048, 2048)],
|
||||
"portrait": [(640, 1536), (680, 2048), (768, 1344), (832, 1216), (896, 1152), (1024, 1536), (1024, 2048), (1080, 1920), (1152, 2048), (1200, 1800), (1368, 2048), (1464, 2048), (1536, 2048), (1608, 2048), (1640, 2048), (1704, 2048)],
|
||||
"landscape": [(1152, 896), (1216, 832), (1280, 720), (1344, 768), (1536, 640), (1536, 1024), (1800, 1200), (1920, 1080), (2048, 680), (2048, 1024), (2048, 1152), (2048, 1368), (2048, 1464), (2048, 1536), (2048, 1608), (2048, 1640), (2048, 1704)],
|
||||
"constraints": {"divisible_by": 8, "min": 256, "max": 2048}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def gcd(a, b):
|
||||
"""
|
||||
Calculate greatest common divisor using Euclidean algorithm.
|
||||
|
||||
Args:
|
||||
a (int): First number
|
||||
b (int): Second number
|
||||
|
||||
Returns:
|
||||
int: Greatest common divisor
|
||||
"""
|
||||
while b != 0:
|
||||
a, b = b, a % b
|
||||
return a
|
||||
|
||||
|
||||
def calculate_aspect_ratio(width, height):
|
||||
"""
|
||||
Calculate simplified aspect ratio from dimensions, using nearest common ratio.
|
||||
|
||||
Args:
|
||||
width (int): Width in pixels
|
||||
height (int): Height in pixels
|
||||
|
||||
Returns:
|
||||
str: Aspect ratio like "16:9" or "1:1"
|
||||
"""
|
||||
# Calculate actual ratio as decimal
|
||||
actual_ratio = width / height
|
||||
|
||||
# Common aspect ratios (ratio_value, "width:height" string)
|
||||
common_ratios = [
|
||||
(1.0, "1:1"), # Square
|
||||
(1.25, "5:4"), # 1.25
|
||||
(1.33, "4:3"), # 1.333...
|
||||
(1.5, "3:2"), # 1.5
|
||||
(1.6, "16:10"), # 1.6
|
||||
(1.78, "16:9"), # 1.777...
|
||||
(2.0, "2:1"), # 2.0
|
||||
(2.35, "21:9"), # 2.333... (ultrawide)
|
||||
(2.4, "12:5"), # 2.4
|
||||
(3.0, "3:1"), # 3.0
|
||||
# Portrait ratios
|
||||
(0.75, "3:4"), # 0.75
|
||||
(0.67, "2:3"), # 0.666...
|
||||
(0.625, "5:8"), # 0.625
|
||||
(0.56, "9:16"), # 0.5625
|
||||
(0.5, "1:2"), # 0.5
|
||||
(0.42, "5:12"), # 0.4166...
|
||||
(0.33, "1:3"), # 0.333... (panoramic)
|
||||
]
|
||||
|
||||
# Find closest common ratio by absolute difference
|
||||
closest_ratio = min(common_ratios, key=lambda r: abs(actual_ratio - r[0]))
|
||||
return closest_ratio[1]
|
||||
|
||||
|
||||
def format_resolution(width, height):
|
||||
"""
|
||||
Format resolution tuple into display string with aspect ratio and orientation.
|
||||
Uses fixed-width formatting for better alignment in dropdowns.
|
||||
|
||||
Args:
|
||||
width (int): Width in pixels
|
||||
height (int): Height in pixels
|
||||
|
||||
Returns:
|
||||
str: Formatted string like "1920x1080 (16:9 Landscape)" with consistent spacing
|
||||
"""
|
||||
aspect_ratio = calculate_aspect_ratio(width, height)
|
||||
|
||||
if width == height:
|
||||
orientation = "Square"
|
||||
elif width < height:
|
||||
orientation = "Portrait"
|
||||
else:
|
||||
orientation = "Landscape"
|
||||
|
||||
# Format with fixed width for better alignment (e.g., "1920x1080 ")
|
||||
# Most resolutions are 4 digits, so we pad to 9 characters (4x4 + 'x')
|
||||
resolution_str = f"{width}x{height}"
|
||||
padded_resolution = resolution_str.ljust(13) # Pad to 13 chars for alignment
|
||||
|
||||
return f"{padded_resolution}({aspect_ratio} {orientation})"
|
||||
|
||||
|
||||
def get_resolution_list(model_name):
|
||||
"""
|
||||
Generate ordered list of resolution strings for a specific model.
|
||||
|
||||
Args:
|
||||
model_name (str): Name of the model (or "All" for all unique resolutions)
|
||||
|
||||
Returns:
|
||||
list: Formatted resolution strings in order: square, portrait, landscape
|
||||
"""
|
||||
if model_name == "All":
|
||||
return get_all_resolutions()
|
||||
|
||||
if model_name not in MODEL_RESOLUTIONS:
|
||||
return []
|
||||
|
||||
model_data = MODEL_RESOLUTIONS[model_name]
|
||||
resolutions = []
|
||||
|
||||
# Order: square first, then portrait, then landscape
|
||||
for category in ["square", "portrait", "landscape"]:
|
||||
for width, height in model_data[category]:
|
||||
resolutions.append(format_resolution(width, height))
|
||||
|
||||
return resolutions
|
||||
|
||||
|
||||
def get_default_resolution(model_name):
|
||||
"""
|
||||
Get the default/native resolution for a specific model.
|
||||
|
||||
Args:
|
||||
model_name (str): Name of the model
|
||||
|
||||
Returns:
|
||||
str: Formatted resolution string for the model's native resolution
|
||||
"""
|
||||
# Model-specific native/optimal resolutions
|
||||
default_resolutions = {
|
||||
"Flux": (1024, 1024), # Flux native
|
||||
"Qwen Image": (1328, 1328), # Qwen native
|
||||
"Z-Image": (1024, 1024), # Z-Image native
|
||||
"SD 1.5": (512, 512), # SD 1.5 native
|
||||
"SDXL": (1024, 1024), # SDXL native
|
||||
"All": (1024, 1024), # Default for "All"
|
||||
}
|
||||
|
||||
if model_name in default_resolutions:
|
||||
width, height = default_resolutions[model_name]
|
||||
return format_resolution(width, height)
|
||||
|
||||
# Fallback to 1024x1024
|
||||
return format_resolution(1024, 1024)
|
||||
|
||||
|
||||
def get_all_resolutions():
|
||||
"""
|
||||
Get all unique resolution strings across all models, sorted by dimensions.
|
||||
|
||||
Returns:
|
||||
list: All unique resolution strings sorted by total pixels
|
||||
"""
|
||||
unique_resolutions = {}
|
||||
|
||||
# Collect all unique width×height pairs
|
||||
for model_name in MODEL_RESOLUTIONS.keys():
|
||||
model_data = MODEL_RESOLUTIONS[model_name]
|
||||
for category in ["square", "portrait", "landscape"]:
|
||||
for width, height in model_data[category]:
|
||||
key = (width, height)
|
||||
if key not in unique_resolutions:
|
||||
unique_resolutions[key] = format_resolution(width, height)
|
||||
|
||||
# Sort by total pixels, then by width
|
||||
sorted_resolutions = sorted(
|
||||
unique_resolutions.items(),
|
||||
key=lambda item: (item[0][0] * item[0][1], item[0][0])
|
||||
)
|
||||
|
||||
return [res_str for _, res_str in sorted_resolutions]
|
||||
|
||||
|
||||
def parse_resolution_string(resolution_str):
|
||||
"""
|
||||
Parse formatted resolution string back to width, height integers.
|
||||
|
||||
Args:
|
||||
resolution_str (str): Format "1920x1080 (16:9 Landscape)" with possible padding
|
||||
|
||||
Returns:
|
||||
tuple: (width, height)
|
||||
|
||||
Raises:
|
||||
ValueError: If string format is invalid
|
||||
"""
|
||||
try:
|
||||
# Format: "1920x1080 (16:9 Landscape)" - may have padding spaces
|
||||
# Extract the dimension part (before the opening parenthesis)
|
||||
dimension_part = resolution_str.split("(")[0].strip()
|
||||
|
||||
if not dimension_part or 'x' not in dimension_part:
|
||||
raise ValueError(f"No dimension part found in: {resolution_str}")
|
||||
|
||||
# Split on 'x' and convert to integers
|
||||
width, height = map(int, dimension_part.split("x"))
|
||||
|
||||
return (width, height)
|
||||
except (ValueError, IndexError) as e:
|
||||
raise ValueError(f"Invalid resolution format: {resolution_str}")
|
||||
|
||||
|
||||
class ResolutionSelector:
|
||||
"""
|
||||
Enhanced resolution selector supporting multiple image generation models.
|
||||
Provides model-specific resolution presets, custom dimension inputs, and empty latent output.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize device for latent tensor generation."""
|
||||
if COMFY_AVAILABLE:
|
||||
self.device = comfy.model_management.intermediate_device()
|
||||
else:
|
||||
self.device = torch.device("cpu")
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
"""
|
||||
Return a dictionary which contains config for all input fields.
|
||||
|
||||
Returns:
|
||||
dict: Input configuration with required and optional fields
|
||||
"""
|
||||
model_list = ["All"] + list(MODEL_RESOLUTIONS.keys())
|
||||
|
||||
# Get all possible resolutions across all models (JavaScript will filter dynamically)
|
||||
all_resolutions = get_all_resolutions()
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"model": (model_list, {
|
||||
"default": "SDXL"
|
||||
}),
|
||||
"resolution": (all_resolutions, {
|
||||
"default": "1024x1024 (1:1 Square)"
|
||||
}),
|
||||
"resolution_multiplier": (["1x", "2x", "3x", "4x"], {
|
||||
"default": "1x"
|
||||
}),
|
||||
"batch_size": ("INT", {
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 64,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"custom_width": ("INT", {
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 4096,
|
||||
"step": 8,
|
||||
"display": "number"
|
||||
}),
|
||||
"custom_height": ("INT", {
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 4096,
|
||||
"step": 8,
|
||||
"display": "number"
|
||||
}),
|
||||
"custom_multiplier": (["1x", "2x", "3x", "4x"], {
|
||||
"default": "1x"
|
||||
}),
|
||||
"custom_batch": ("INT", {
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 64,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "INT", "LATENT", "INT", "INT", "LATENT")
|
||||
RETURN_NAMES = ("width", "height", "latent", "custom_width", "custom_height", "custom_latent")
|
||||
FUNCTION = "select_resolution"
|
||||
CATEGORY = "utils"
|
||||
|
||||
def select_resolution(self, model, resolution, resolution_multiplier="1x", batch_size=1, custom_width=0, custom_height=0, custom_multiplier="1x", custom_batch=1):
|
||||
"""
|
||||
Select and validate resolution, generate outputs.
|
||||
|
||||
Args:
|
||||
model (str): Selected model name
|
||||
resolution (str): Selected preset resolution string
|
||||
resolution_multiplier (str): Multiplier for resolution (1x-4x)
|
||||
batch_size (int): Number of latent samples for preset resolution (default: 1)
|
||||
custom_width (int, optional): Custom width override
|
||||
custom_height (int, optional): Custom height override
|
||||
custom_multiplier (str, optional): Multiplier for custom dimensions (1x-4x)
|
||||
custom_batch (int, optional): Number of latent samples for custom resolution (default: 1)
|
||||
|
||||
Returns:
|
||||
tuple: (width: int, height: int, latent: dict, custom_width: int, custom_height: int, custom_latent: dict)
|
||||
"""
|
||||
# Parse multipliers (e.g., "2x" -> 2)
|
||||
multiplier = int(resolution_multiplier.replace("x", ""))
|
||||
custom_mult = int(custom_multiplier.replace("x", ""))
|
||||
|
||||
# Parse preset resolution
|
||||
width, height = parse_resolution_string(resolution)
|
||||
|
||||
# Apply multiplier to preset resolution
|
||||
width *= multiplier
|
||||
height *= multiplier
|
||||
|
||||
# Generate latent for preset resolution with batch size
|
||||
latent = self._generate_empty_latent(width, height, batch_size)
|
||||
|
||||
# Determine final custom dimensions
|
||||
if custom_width > 0 and custom_height > 0:
|
||||
# Use custom dimensions with custom multiplier
|
||||
final_custom_width = custom_width * custom_mult
|
||||
final_custom_height = custom_height * custom_mult
|
||||
|
||||
# Validate against model constraints (if not "All" model)
|
||||
if model != "All":
|
||||
self._validate_dimensions(model, final_custom_width, final_custom_height)
|
||||
|
||||
# Generate custom latent with custom batch size
|
||||
custom_latent = self._generate_empty_latent(final_custom_width, final_custom_height, custom_batch)
|
||||
|
||||
return (width, height, latent, final_custom_width, final_custom_height, custom_latent)
|
||||
else:
|
||||
# No custom dimensions, return zeros and minimal empty custom latent
|
||||
custom_latent = self._generate_empty_latent(1, 1, 1)
|
||||
|
||||
return (width, height, latent, 0, 0, custom_latent)
|
||||
|
||||
def _validate_dimensions(self, model, width, height):
|
||||
"""
|
||||
Validate dimensions against model-specific constraints.
|
||||
|
||||
Args:
|
||||
model (str): Model name
|
||||
width (int): Width in pixels
|
||||
height (int): Height in pixels
|
||||
|
||||
Raises:
|
||||
ValueError: If dimensions violate model constraints
|
||||
"""
|
||||
if model not in MODEL_RESOLUTIONS:
|
||||
return
|
||||
|
||||
constraints = MODEL_RESOLUTIONS[model]["constraints"]
|
||||
divisible_by = constraints.get("divisible_by", 8)
|
||||
min_dim = constraints.get("min", 64)
|
||||
max_dim = constraints.get("max", 4096)
|
||||
|
||||
# Check divisibility
|
||||
if width % divisible_by != 0:
|
||||
raise ValueError(
|
||||
f"{model} requires width divisible by {divisible_by}. "
|
||||
f"Got {width} (remainder: {width % divisible_by})"
|
||||
)
|
||||
|
||||
if height % divisible_by != 0:
|
||||
raise ValueError(
|
||||
f"{model} requires height divisible by {divisible_by}. "
|
||||
f"Got {height} (remainder: {height % divisible_by})"
|
||||
)
|
||||
|
||||
# Check bounds
|
||||
if width < min_dim or width > max_dim:
|
||||
raise ValueError(
|
||||
f"{model} requires width between {min_dim} and {max_dim}. Got {width}"
|
||||
)
|
||||
|
||||
if height < min_dim or height > max_dim:
|
||||
raise ValueError(
|
||||
f"{model} requires height between {min_dim} and {max_dim}. Got {height}"
|
||||
)
|
||||
|
||||
def _generate_empty_latent(self, width, height, batch_size=1):
|
||||
"""
|
||||
Generate empty latent tensor for VAE input.
|
||||
|
||||
Args:
|
||||
width (int): Image width in pixels
|
||||
height (int): Image height in pixels
|
||||
batch_size (int): Number of latent samples (default: 1)
|
||||
|
||||
Returns:
|
||||
dict: LATENT dict with 'samples' key containing empty tensor
|
||||
"""
|
||||
# Latent space is 1/8 the image dimensions for SD-based models
|
||||
latent_width = width // 8
|
||||
latent_height = height // 8
|
||||
|
||||
# Shape: [batch_size, channels=4, height//8, width//8]
|
||||
latent_tensor = torch.zeros(
|
||||
[batch_size, 4, latent_height, latent_width],
|
||||
device=self.device
|
||||
)
|
||||
|
||||
return {"samples": latent_tensor}
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ResolutionSelector": ResolutionSelector,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ResolutionSelector": "Resolution Selector Plus",
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for ResolutionSelector enhancements
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, '.')
|
||||
|
||||
# Mock torch module for testing without ComfyUI environment
|
||||
class MockTorch:
|
||||
class device:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
sys.modules['torch'] = MockTorch()
|
||||
sys.modules['comfy'] = type('module', (), {'model_management': None})()
|
||||
|
||||
from resolution_selector import (
|
||||
gcd,
|
||||
calculate_aspect_ratio,
|
||||
format_resolution,
|
||||
get_resolution_list,
|
||||
get_all_resolutions,
|
||||
parse_resolution_string,
|
||||
MODEL_RESOLUTIONS
|
||||
)
|
||||
|
||||
def test_gcd():
|
||||
"""Test GCD function"""
|
||||
print("Testing GCD function:")
|
||||
assert gcd(1920, 1080) == 120, "GCD of 1920, 1080 should be 120"
|
||||
assert gcd(1024, 1024) == 1024, "GCD of 1024, 1024 should be 1024"
|
||||
assert gcd(16, 9) == 1, "GCD of 16, 9 should be 1"
|
||||
print(" ✓ GCD tests passed")
|
||||
|
||||
def test_aspect_ratio():
|
||||
"""Test aspect ratio calculation"""
|
||||
print("\nTesting aspect ratio calculation:")
|
||||
assert calculate_aspect_ratio(1920, 1080) == "16:9", "1920x1080 should be 16:9"
|
||||
assert calculate_aspect_ratio(1024, 1024) == "1:1", "1024x1024 should be 1:1"
|
||||
assert calculate_aspect_ratio(1280, 720) == "16:9", "1280x720 should be 16:9"
|
||||
assert calculate_aspect_ratio(1536, 1024) == "3:2", "1536x1024 should be 3:2"
|
||||
print(" ✓ Aspect ratio tests passed")
|
||||
|
||||
def test_format_resolution():
|
||||
"""Test resolution formatting"""
|
||||
print("\nTesting resolution formatting:")
|
||||
result = format_resolution(1920, 1080)
|
||||
print(f" 1920x1080 → '{result}'")
|
||||
# With padding, should be "1920x1080 (16:9 Landscape)" - 13 chars total for resolution part
|
||||
assert "(16:9 Landscape)" in result, f"Should contain aspect ratio and orientation"
|
||||
assert result.startswith("1920x1080"), f"Should start with resolution"
|
||||
|
||||
result = format_resolution(1024, 1024)
|
||||
print(f" 1024x1024 → '{result}'")
|
||||
assert "(1:1 Square)" in result, f"Should contain aspect ratio and orientation"
|
||||
assert result.startswith("1024x1024"), f"Should start with resolution"
|
||||
|
||||
result = format_resolution(1080, 1920)
|
||||
print(f" 1080x1920 → '{result}'")
|
||||
assert "(9:16 Portrait)" in result, f"Should contain aspect ratio and orientation"
|
||||
assert result.startswith("1080x1920"), f"Should start with resolution"
|
||||
print(" ✓ Format resolution tests passed")
|
||||
|
||||
def test_parse_resolution():
|
||||
"""Test resolution string parsing"""
|
||||
print("\nTesting resolution string parsing:")
|
||||
# Test with padding (as it will be in the actual dropdown)
|
||||
width, height = parse_resolution_string("1920x1080 (16:9 Landscape)")
|
||||
assert width == 1920 and height == 1080, "Should parse 1920x1080 with padding"
|
||||
|
||||
width, height = parse_resolution_string("1024x1024 (1:1 Square)")
|
||||
assert width == 1024 and height == 1024, "Should parse 1024x1024 with padding"
|
||||
|
||||
# Test without padding (backward compatibility)
|
||||
width, height = parse_resolution_string("1920x1080 (16:9 Landscape)")
|
||||
assert width == 1920 and height == 1080, "Should parse 1920x1080 without padding"
|
||||
print(" ✓ Parse resolution tests passed")
|
||||
|
||||
def test_model_resolutions():
|
||||
"""Test model resolution lists"""
|
||||
print("\nTesting model resolution lists:")
|
||||
|
||||
# Test individual model
|
||||
flux_res = get_resolution_list("Flux")
|
||||
print(f" Flux has {len(flux_res)} resolutions")
|
||||
assert len(flux_res) > 0, "Flux should have resolutions"
|
||||
# Flux uses 16-pixel divisibility, so 1920x1080 becomes 1920x1088
|
||||
assert any("1920x1088" in r for r in flux_res), "Flux should have 1920x1088 (Full HD adapted to 16px divisibility)"
|
||||
|
||||
# Test All model
|
||||
all_res = get_resolution_list("All")
|
||||
print(f" 'All' has {len(all_res)} unique resolutions")
|
||||
assert len(all_res) > len(flux_res), "'All' should have more resolutions than individual models"
|
||||
|
||||
# Verify no duplicates in All
|
||||
assert len(all_res) == len(set(all_res)), "'All' should have no duplicates"
|
||||
print(" ✓ Model resolution tests passed")
|
||||
|
||||
def test_new_resolutions():
|
||||
"""Test that new resolutions were added"""
|
||||
print("\nTesting new resolutions up to 1920x1080:")
|
||||
|
||||
for model_name, model_data in MODEL_RESOLUTIONS.items():
|
||||
resolutions = get_resolution_list(model_name)
|
||||
has_1080p = any("1920x1080" in r or "1080x1920" in r for r in resolutions)
|
||||
print(f" {model_name}: {len(resolutions)} resolutions, has 1080p: {has_1080p}")
|
||||
|
||||
print(" ✓ New resolutions verified")
|
||||
|
||||
def test_all_resolutions_unique():
|
||||
"""Test that 'All' model returns unique resolutions"""
|
||||
print("\nTesting 'All' model uniqueness:")
|
||||
all_res = get_all_resolutions()
|
||||
|
||||
# Count occurrences
|
||||
dimensions = []
|
||||
for res in all_res:
|
||||
width, height = parse_resolution_string(res)
|
||||
dimensions.append((width, height))
|
||||
|
||||
# Check for duplicates
|
||||
unique_dimensions = set(dimensions)
|
||||
assert len(dimensions) == len(unique_dimensions), "Should have no duplicate dimensions"
|
||||
print(f" ✓ All {len(all_res)} resolutions are unique")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("ResolutionSelector Enhancement Tests")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
test_gcd()
|
||||
test_aspect_ratio()
|
||||
test_format_resolution()
|
||||
test_parse_resolution()
|
||||
test_model_resolutions()
|
||||
test_new_resolutions()
|
||||
test_all_resolutions_unique()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("✓ ALL TESTS PASSED!")
|
||||
print("=" * 60)
|
||||
|
||||
except AssertionError as e:
|
||||
print(f"\n✗ TEST FAILED: {e}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n✗ ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
@@ -1,239 +0,0 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
|
||||
// Model-specific resolution presets with constraints
|
||||
// Includes: model-optimized sizes + photo print (4x6, 5x7, 8x10) + digital/social + canvas art ratios
|
||||
const MODEL_RESOLUTIONS = {
|
||||
"Flux": {
|
||||
"square": [[512, 512], [768, 768], [1024, 1024], [1088, 1088], [1280, 1280], [1536, 1536], [1920, 1920], [2048, 2048]],
|
||||
"portrait": [[688, 2048], [768, 1344], [832, 1216], [896, 1152], [928, 1664], [1024, 1536], [1024, 1792], [1024, 2048], [1088, 1920], [1152, 2048], [1200, 1792], [1360, 2048], [1456, 2048], [1536, 2048], [1616, 2048], [1632, 2048], [1712, 2048]],
|
||||
"landscape": [[1280, 720], [1344, 768], [1216, 832], [1152, 896], [1536, 1024], [1664, 928], [1792, 1024], [1792, 1200], [1920, 1088], [2048, 688], [2048, 1024], [2048, 1152], [2048, 1360], [2048, 1456], [2048, 1536], [2048, 1616], [2048, 1632], [2048, 1712]],
|
||||
},
|
||||
"Qwen Image": {
|
||||
"square": [[1024, 1024], [1080, 1080], [1280, 1280], [1328, 1328], [1536, 1536], [1920, 1920], [2048, 2048]],
|
||||
"portrait": [[680, 2048], [928, 1664], [1024, 1536], [1024, 2048], [1080, 1920], [1140, 1472], [1152, 2048], [1200, 1800], [1368, 2048], [1464, 2048], [1536, 2048], [1608, 2048], [1640, 2048], [1704, 2048]],
|
||||
"landscape": [[1280, 720], [1472, 1140], [1536, 1024], [1664, 928], [1800, 1200], [1920, 1080], [2048, 680], [2048, 1024], [2048, 1152], [2048, 1368], [2048, 1464], [2048, 1536], [2048, 1608], [2048, 1640], [2048, 1704]],
|
||||
},
|
||||
"Z-Image": {
|
||||
"square": [[512, 512], [768, 768], [1024, 1024], [1080, 1080], [1280, 1280], [1536, 1536], [1920, 1920], [2048, 2048]],
|
||||
"portrait": [[680, 2048], [720, 1280], [768, 1024], [1024, 2048], [1080, 1920], [1152, 2048], [1200, 1800], [1368, 2048], [1464, 2048], [1536, 2048], [1608, 2048], [1640, 2048], [1704, 2048]],
|
||||
"landscape": [[1024, 768], [1280, 720], [1800, 1200], [1920, 1080], [2048, 680], [2048, 1024], [2048, 1152], [2048, 1368], [2048, 1464], [2048, 1536], [2048, 1608], [2048, 1640], [2048, 1704]],
|
||||
},
|
||||
"SD 1.5": {
|
||||
"square": [[512, 512], [768, 768], [1024, 1024], [1080, 1080], [1280, 1280], [1536, 1536]],
|
||||
"portrait": [[512, 768], [512, 682], [512, 1024], [680, 2048], [768, 1024], [768, 1344], [1024, 2048], [1080, 1920], [1200, 1800], [1368, 2048], [1464, 2048], [1536, 2048], [1608, 2048], [1640, 2048], [1704, 2048]],
|
||||
"landscape": [[768, 512], [1024, 512], [1024, 768], [1280, 720], [1344, 768], [1536, 512], [1800, 1200], [1920, 1080], [2048, 680], [2048, 1024], [2048, 1368], [2048, 1464], [2048, 1536], [2048, 1608], [2048, 1640], [2048, 1704]],
|
||||
},
|
||||
"SDXL": {
|
||||
"square": [[1024, 1024], [1080, 1080], [1280, 1280], [1536, 1536], [1920, 1920], [2048, 2048]],
|
||||
"portrait": [[640, 1536], [680, 2048], [768, 1344], [832, 1216], [896, 1152], [1024, 1536], [1024, 2048], [1080, 1920], [1152, 2048], [1200, 1800], [1368, 2048], [1464, 2048], [1536, 2048], [1608, 2048], [1640, 2048], [1704, 2048]],
|
||||
"landscape": [[1152, 896], [1216, 832], [1280, 720], [1344, 768], [1536, 640], [1536, 1024], [1800, 1200], [1920, 1080], [2048, 680], [2048, 1024], [2048, 1152], [2048, 1368], [2048, 1464], [2048, 1536], [2048, 1608], [2048, 1640], [2048, 1704]],
|
||||
}
|
||||
};
|
||||
|
||||
function gcd(a, b) {
|
||||
while (b !== 0) {
|
||||
const temp = b;
|
||||
b = a % b;
|
||||
a = temp;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function calculateAspectRatio(width, height) {
|
||||
// Calculate actual ratio as decimal
|
||||
const actualRatio = width / height;
|
||||
|
||||
// Common aspect ratios [ratio_value, "width:height" string]
|
||||
const commonRatios = [
|
||||
[1.0, "1:1"], // Square
|
||||
[1.25, "5:4"], // 1.25
|
||||
[1.33, "4:3"], // 1.333...
|
||||
[1.5, "3:2"], // 1.5
|
||||
[1.6, "16:10"], // 1.6
|
||||
[1.78, "16:9"], // 1.777...
|
||||
[2.0, "2:1"], // 2.0
|
||||
[2.35, "21:9"], // 2.333... (ultrawide)
|
||||
[2.4, "12:5"], // 2.4
|
||||
[3.0, "3:1"], // 3.0
|
||||
// Portrait ratios
|
||||
[0.75, "3:4"], // 0.75
|
||||
[0.67, "2:3"], // 0.666...
|
||||
[0.625, "5:8"], // 0.625
|
||||
[0.56, "9:16"], // 0.5625
|
||||
[0.5, "1:2"], // 0.5
|
||||
[0.42, "5:12"], // 0.4166...
|
||||
[0.33, "1:3"], // 0.333... (panoramic)
|
||||
];
|
||||
|
||||
// Find closest common ratio by absolute difference
|
||||
let closestRatio = commonRatios[0];
|
||||
let minDiff = Math.abs(actualRatio - closestRatio[0]);
|
||||
|
||||
for (const ratio of commonRatios) {
|
||||
const diff = Math.abs(actualRatio - ratio[0]);
|
||||
if (diff < minDiff) {
|
||||
minDiff = diff;
|
||||
closestRatio = ratio;
|
||||
}
|
||||
}
|
||||
|
||||
return closestRatio[1];
|
||||
}
|
||||
|
||||
function formatResolution(width, height) {
|
||||
const aspectRatio = calculateAspectRatio(width, height);
|
||||
let orientation;
|
||||
|
||||
if (width === height) {
|
||||
orientation = "Square";
|
||||
} else if (width < height) {
|
||||
orientation = "Portrait";
|
||||
} else {
|
||||
orientation = "Landscape";
|
||||
}
|
||||
|
||||
// Format with fixed width for better alignment
|
||||
const resolutionStr = `${width}x${height}`;
|
||||
const paddedResolution = resolutionStr.padEnd(13, ' '); // Pad to 13 chars for alignment
|
||||
|
||||
return `${paddedResolution}(${aspectRatio} ${orientation})`;
|
||||
}
|
||||
|
||||
function getAllResolutions() {
|
||||
const uniqueResolutions = new Map();
|
||||
|
||||
// Collect all unique width×height pairs
|
||||
for (const modelName in MODEL_RESOLUTIONS) {
|
||||
const modelData = MODEL_RESOLUTIONS[modelName];
|
||||
for (const category of ["square", "portrait", "landscape"]) {
|
||||
if (modelData[category]) {
|
||||
for (const [w, h] of modelData[category]) {
|
||||
const key = `${w}x${h}`;
|
||||
if (!uniqueResolutions.has(key)) {
|
||||
uniqueResolutions.set(key, { width: w, height: h, pixels: w * h });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by total pixels, then by width
|
||||
const sorted = Array.from(uniqueResolutions.values())
|
||||
.sort((a, b) => {
|
||||
if (a.pixels !== b.pixels) return a.pixels - b.pixels;
|
||||
return a.width - b.width;
|
||||
});
|
||||
|
||||
return sorted.map(({ width, height }) => formatResolution(width, height));
|
||||
}
|
||||
|
||||
function getResolutionsForModel(model) {
|
||||
if (model === "All") {
|
||||
return getAllResolutions();
|
||||
}
|
||||
|
||||
const modelData = MODEL_RESOLUTIONS[model];
|
||||
if (!modelData) return [];
|
||||
|
||||
const resolutions = [];
|
||||
for (const category of ["square", "portrait", "landscape"]) {
|
||||
if (modelData[category]) {
|
||||
for (const [w, h] of modelData[category]) {
|
||||
resolutions.push(formatResolution(w, h));
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolutions;
|
||||
}
|
||||
|
||||
function getDefaultResolution(model) {
|
||||
// Model-specific native/optimal resolutions
|
||||
const defaultResolutions = {
|
||||
"Flux": [1024, 1024],
|
||||
"Qwen Image": [1328, 1328],
|
||||
"Z-Image": [1024, 1024],
|
||||
"SD 1.5": [512, 512],
|
||||
"SDXL": [1024, 1024],
|
||||
"All": [1024, 1024]
|
||||
};
|
||||
|
||||
if (defaultResolutions[model]) {
|
||||
const [w, h] = defaultResolutions[model];
|
||||
return formatResolution(w, h);
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return formatResolution(1024, 1024);
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "ResolutionSelector.DynamicDropdown",
|
||||
|
||||
async nodeCreated(node) {
|
||||
if (node.comfyClass !== "ResolutionSelector") return;
|
||||
|
||||
// Set wider default node width to prevent text cutoff
|
||||
node.setSize([400, node.size[1]]);
|
||||
|
||||
const modelWidget = node.widgets.find(w => w.name === "model");
|
||||
const resolutionWidget = node.widgets.find(w => w.name === "resolution");
|
||||
|
||||
if (!modelWidget || !resolutionWidget) {
|
||||
console.error("ResolutionSelector: Required widgets not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const origCallback = modelWidget.callback;
|
||||
|
||||
const updateResolutions = (modelValue) => {
|
||||
const resolutions = getResolutionsForModel(modelValue);
|
||||
|
||||
if (resolutions.length === 0) {
|
||||
console.warn(`ResolutionSelector: No resolutions found for model ${modelValue}`);
|
||||
return;
|
||||
}
|
||||
|
||||
resolutionWidget.options.values = resolutions;
|
||||
|
||||
// Set to model-specific default resolution
|
||||
const defaultRes = getDefaultResolution(modelValue);
|
||||
if (resolutions.includes(defaultRes)) {
|
||||
resolutionWidget.value = defaultRes;
|
||||
} else if (!resolutions.includes(resolutionWidget.value)) {
|
||||
// Fallback to first resolution if default not found
|
||||
resolutionWidget.value = resolutions[0];
|
||||
}
|
||||
|
||||
node.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
modelWidget.callback = function(value) {
|
||||
if (origCallback) {
|
||||
origCallback.apply(this, arguments);
|
||||
}
|
||||
updateResolutions(value);
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
updateResolutions(modelWidget.value);
|
||||
}, 10);
|
||||
},
|
||||
|
||||
async loadedGraphNode(node) {
|
||||
if (node.comfyClass !== "ResolutionSelector") return;
|
||||
|
||||
const modelWidget = node.widgets.find(w => w.name === "model");
|
||||
if (modelWidget) {
|
||||
const resolutions = getResolutionsForModel(modelWidget.value);
|
||||
const resolutionWidget = node.widgets.find(w => w.name === "resolution");
|
||||
|
||||
if (resolutionWidget && resolutions.length > 0) {
|
||||
resolutionWidget.options.values = resolutions;
|
||||
|
||||
if (!resolutions.includes(resolutionWidget.value)) {
|
||||
resolutionWidget.value = resolutions[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user