new list utility nodes (#13)
* add List-related nodes for Checkpoints / LoRAs / Samplers/ Schedulers * wiki: reorg XY Plot 3rd tutorial and add example * bump version
@@ -4,6 +4,11 @@
|
||||
|
||||
## v0.0
|
||||
|
||||
## v0.0.5 (2025-01-29)
|
||||
|
||||
- new List utility nodes: Random Seeds / Checkpoints / LoRAs / Samplers/ Schedulers
|
||||
- reorganize / improve wiki
|
||||
|
||||
## v0.0.4 (2025-01-28): better wiki
|
||||
|
||||
- replace repo banner and icon
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
# News
|
||||
|
||||
- 2025-01-28 - v0.0.5: added node `List: Random Seeds`
|
||||
- 2025-01-29 - v0.0.5:
|
||||
- new List utility nodes: Random Seeds / Checkpoints / LoRAs / Samplers/ Schedulers
|
||||
- reorganize / improve wiki
|
||||
|
||||
# Node overview
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ make_node_list(Path(__file__).parent / 'node_list.json')
|
||||
load_api()
|
||||
|
||||
print(
|
||||
' 🧬 {}: {} hybrid nodes successfully cloned'.format(
|
||||
' ⚙️ {}: {} carefully crafted nodes available'.format(
|
||||
CATEGORY, len(NODE_CLASS_MAPPINGS)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -82,6 +82,16 @@ const customLabels = {
|
||||
nb_elements: 'nb of elements',
|
||||
},
|
||||
},
|
||||
ListCheckpoints: {
|
||||
widget: {
|
||||
with_extension: 'with file extension?',
|
||||
},
|
||||
},
|
||||
ListLoras: {
|
||||
widget: {
|
||||
with_extension: 'with file extension?',
|
||||
},
|
||||
},
|
||||
OutputConfigBackend: {
|
||||
widget: {
|
||||
file_path: 'JSON or YAML file (backend)',
|
||||
@@ -95,31 +105,6 @@ const customLabels = {
|
||||
show_config: 'show config',
|
||||
},
|
||||
},
|
||||
OutputConfigAuto: {
|
||||
widget: {
|
||||
auto_connect: 'auto-connect?',
|
||||
multi_nodes: 'if multiple nodes found',
|
||||
error_display: 'display errors',
|
||||
show_config: 'show config',
|
||||
},
|
||||
},
|
||||
NodeConfigApplyLocal: {
|
||||
widget: {
|
||||
error_display: 'display errors',
|
||||
check_level: 'reject on',
|
||||
},
|
||||
},
|
||||
MultiConfigFetch: {
|
||||
widget: {
|
||||
node_by: 'identify nodes by',
|
||||
},
|
||||
},
|
||||
MultiConfigApplyLocal: {
|
||||
widget: {
|
||||
error_display: 'display errors',
|
||||
check_level: 'reject on',
|
||||
},
|
||||
},
|
||||
GenericQueue: {
|
||||
input: {
|
||||
input_list: 'input list',
|
||||
@@ -238,6 +223,26 @@ const customLooks = {
|
||||
list: { shape: 'grid' },
|
||||
},
|
||||
},
|
||||
ListCheckpoints: {
|
||||
output: {
|
||||
list: { shape: 'grid' },
|
||||
},
|
||||
},
|
||||
ListLoras: {
|
||||
output: {
|
||||
list: { shape: 'grid' },
|
||||
},
|
||||
},
|
||||
ListSamplers: {
|
||||
output: {
|
||||
list: { shape: 'grid' },
|
||||
},
|
||||
},
|
||||
ListSchedulers: {
|
||||
output: {
|
||||
list: { shape: 'grid' },
|
||||
},
|
||||
},
|
||||
};
|
||||
function setNodeLabels(nodeType, nodeData) {
|
||||
const nodeName = nodeData.name;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { findWidget, loadTextFile } from '../shared/utils.js';
|
||||
import { makeComboWidget } from '../widgets/factories.js';
|
||||
export function ListFromMultiline(nodeType) {
|
||||
const original_onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function (...args) {
|
||||
@@ -13,3 +14,78 @@ export function ListFromMultiline(nodeType) {
|
||||
};
|
||||
};
|
||||
}
|
||||
export function ListFromSelection(nodeType, selection_type) {
|
||||
const isCombo = (widget) => widget.name.startsWith(`${selection_type}_`.toLowerCase());
|
||||
const findCombos = (node) => node.widgets.filter((w) => isCombo(w));
|
||||
const addCombo = (node, selectionWidget) => {
|
||||
const index = findCombos(node).length;
|
||||
const width = node.size[0];
|
||||
const widget = makeComboWidget(node, `${selection_type}_${index}`.toLowerCase(), ['none', ...selectionWidget.all], 0);
|
||||
node.setSize([width, node.size[1]]);
|
||||
widget.label = `${selection_type} #${index + 1}`;
|
||||
widget.options.serialize = false;
|
||||
return widget;
|
||||
};
|
||||
const refreshSelection = (node, selectionWidget) => {
|
||||
const selected = [];
|
||||
let widgetIndex = 0;
|
||||
let comboIndex = 0;
|
||||
while (widgetIndex < node.widgets.length) {
|
||||
const widget = node.widgets[widgetIndex];
|
||||
if (!isCombo(widget)) {
|
||||
widgetIndex += 1;
|
||||
continue;
|
||||
}
|
||||
if (widget.selected === 0) {
|
||||
node.widgets.splice(widgetIndex, 1);
|
||||
continue;
|
||||
}
|
||||
widget.name = `${selection_type}_${comboIndex}`.toLowerCase();
|
||||
widget.label = `${selection_type} #${comboIndex + 1}`;
|
||||
selected.push(widget.value);
|
||||
widgetIndex += 1;
|
||||
comboIndex += 1;
|
||||
}
|
||||
const last = addCombo(node, selectionWidget);
|
||||
last.onSelected = () => {
|
||||
refreshSelection(node, selectionWidget);
|
||||
};
|
||||
selectionWidget.value = { selected };
|
||||
};
|
||||
const refreshFromSelection = (node, selectionWidget) => {
|
||||
let widgetIndex = 0;
|
||||
while (widgetIndex < node.widgets.length) {
|
||||
const widget = node.widgets[widgetIndex];
|
||||
if (isCombo(widget)) {
|
||||
node.widgets.splice(widgetIndex, 1);
|
||||
continue;
|
||||
}
|
||||
widgetIndex += 1;
|
||||
}
|
||||
const selected = selectionWidget.value.selected;
|
||||
for (const value of selected) {
|
||||
const widget = addCombo(node, selectionWidget);
|
||||
widget.value = value;
|
||||
widget.callback(widget.value);
|
||||
widget.onSelected = () => {
|
||||
refreshSelection(node, selectionWidget);
|
||||
};
|
||||
}
|
||||
const last = addCombo(node, selectionWidget);
|
||||
last.onSelected = () => {
|
||||
refreshSelection(node, selectionWidget);
|
||||
};
|
||||
};
|
||||
const original_onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function (...args) {
|
||||
original_onNodeCreated?.apply(this, ...args);
|
||||
const selectionWidget = findWidget(this, 'selection');
|
||||
refreshFromSelection(this, selectionWidget);
|
||||
};
|
||||
const original_onConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function (...args) {
|
||||
original_onConfigure?.apply(this, ...args);
|
||||
const selectionWidget = findWidget(this, 'selection');
|
||||
refreshFromSelection(this, selectionWidget);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { CATEGORY } from './shared/common.js';
|
||||
import { customizeNode } from './custom.js';
|
||||
import { setupDynamicConnections } from './shared/dynamic_connections.js';
|
||||
import { QUEUE_STATUS } from './widgets/queue.js';
|
||||
import { HIDDEN, BTN, ERROR_DISPLAY } from './widgets/misc.js';
|
||||
import { HIDDEN, BTN, ERROR_DISPLAY, SELECTION_LIST } from './widgets/misc.js';
|
||||
import { OutputConfigBackend, OutputConfigLocal } from './nodes/config.js';
|
||||
import { ListFromMultiline } from './nodes/list.js';
|
||||
import { ListFromMultiline, ListFromSelection } from './nodes/list.js';
|
||||
import { InputMultiline } from './nodes/input.js';
|
||||
import { DebugJSONYAML, DebugWidgetVisibility } from './nodes/debug.js';
|
||||
globalThis[CATEGORY] = { log: 'debug' };
|
||||
@@ -28,6 +28,21 @@ app.registerExtension({
|
||||
start_index: 1,
|
||||
});
|
||||
break;
|
||||
case 'ListFromMultiline':
|
||||
ListFromMultiline(nodeType);
|
||||
break;
|
||||
case 'ListCheckpoints':
|
||||
ListFromSelection(nodeType, 'checkpoint');
|
||||
break;
|
||||
case 'ListLoras':
|
||||
ListFromSelection(nodeType, 'LoRA');
|
||||
break;
|
||||
case 'ListSamplers':
|
||||
ListFromSelection(nodeType, 'sampler');
|
||||
break;
|
||||
case 'ListSchedulers':
|
||||
ListFromSelection(nodeType, 'scheduler');
|
||||
break;
|
||||
case 'InputMultiline':
|
||||
InputMultiline(nodeType);
|
||||
break;
|
||||
@@ -38,9 +53,6 @@ app.registerExtension({
|
||||
start_index: 0,
|
||||
});
|
||||
break;
|
||||
case 'ListFromMultiline':
|
||||
ListFromMultiline(nodeType);
|
||||
break;
|
||||
case 'DebugJSONYAML':
|
||||
DebugJSONYAML(nodeType);
|
||||
break;
|
||||
@@ -63,6 +75,7 @@ app.registerExtension({
|
||||
HIDDEN,
|
||||
BTN,
|
||||
ERROR_DISPLAY,
|
||||
SELECTION_LIST,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { makeButtonWidget, makeComboWidget, } from '../widgets/factories.js';
|
||||
import { makeWidgetAsync } from '../shared/utils.js';
|
||||
export function HIDDEN(node, inputName, inputData, _app) {
|
||||
import { log } from '../shared/common.js';
|
||||
export function HIDDEN(node, inputName, inputData) {
|
||||
const initialValue = inputData[1].value;
|
||||
const widget = {
|
||||
type: 'HIDDEN',
|
||||
@@ -69,3 +70,13 @@ export function ERROR_DISPLAY(node, name = 'error_display') {
|
||||
const widget = makeComboWidget(node, name, ERROR_DISPLAY_CHOICES, 'dialog', () => { }, 'how to display detected errors');
|
||||
return widget;
|
||||
}
|
||||
export function SELECTION_LIST(node, inputName, inputData) {
|
||||
inputData[1].value = { selected: [] };
|
||||
if (!Array.isArray(inputData[1].all) || !inputData[1].all) {
|
||||
log.error("SELECTION_LIST: 'all is invalid");
|
||||
inputData[1].all = [];
|
||||
}
|
||||
const widget = HIDDEN(node, inputName, inputData);
|
||||
widget.all = inputData[1].all;
|
||||
return widget;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
"List: from Elements (lab)": "Create a list from any number of individual elements",
|
||||
"List: Merge (lab)": "Merge any number of lists into one",
|
||||
"List: Limit (lab)": "Limit list to a given size",
|
||||
"List: Random Seeds (lab)": "Return a list of random seeds",
|
||||
"List: Random Seeds (lab)": "Create a list of random seeds",
|
||||
"List: Checkpoints (lab)": "Create a list from selected checkpoint file names",
|
||||
"List: LoRAs (lab)": "Create a list from selected LoRA file names",
|
||||
"List: Samplers (lab)": "Create a list from selected sampler names",
|
||||
"List: Schedulers (lab)": "Create a list from selected scheduler names",
|
||||
"Output Config: Load (lab)": "Load a local JSON or YAML config file, and dynamically generate outputs",
|
||||
"Output Config: Retrieve (backend) (lab)": "Retrieve a JSON or YAML config file from backend, and dynamically generate outputs",
|
||||
"Generic Queue (lab)": "Loop through all values of input list, and send them to output",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "confylab-pack",
|
||||
"private": true,
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"watch": "tsc-watch -p tsconfig.json --onSuccess 'tsc-alias -p tsconfig.json -r build/replacers.js/mock.js'",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfylab-pack"
|
||||
description = "A collection of carefully experimented nodes, to improve your Comfy UX."
|
||||
version = "0.0.4"
|
||||
version = "0.0.5"
|
||||
authors = [{ name = "bugltd" }]
|
||||
license = {file = "LICENSE"}
|
||||
readme = "README.md"
|
||||
@@ -18,4 +18,4 @@ Changelog = "https://github.com/bugltd/ComfyLab-Pack/blob/main/CHANGELOG.md"
|
||||
[tool.comfy]
|
||||
PublisherId = "bugltd"
|
||||
DisplayName = "ComfyLab Pack"
|
||||
Icon = "https://github.com/bugltd/ComfyLab-Pack/blob/main/static/repo_icon.png"
|
||||
Icon = "https://github.com/bugltd/ComfyLab-Pack/blob/main/static/repo_icon_400.png"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import hashlib
|
||||
import math
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
import folder_paths # type: ignore
|
||||
from comfy.samplers import KSampler # type: ignore
|
||||
|
||||
from ..collection.register_nodes import register_node
|
||||
|
||||
@@ -165,56 +169,6 @@ class ListFromMultiline:
|
||||
return (output, len(output))
|
||||
|
||||
|
||||
# @register_node('Input Multiline To Batch', 'list')
|
||||
# class BatchFromMultiline:
|
||||
# def __init__(self):
|
||||
# pass
|
||||
|
||||
# @classmethod
|
||||
# def INPUT_TYPES(s):
|
||||
# return {
|
||||
# 'required': {
|
||||
# 'separator': (
|
||||
# 'STRING',
|
||||
# {'default': ',', 'multiline': False},
|
||||
# ),
|
||||
# 'index': (
|
||||
# 'INT',
|
||||
# {
|
||||
# 'min': 0,
|
||||
# 'default': 0,
|
||||
# },
|
||||
# ),
|
||||
# 'multiline': (
|
||||
# 'STRING',
|
||||
# {'default': '', 'multiline': True},
|
||||
# ),
|
||||
# }
|
||||
# }
|
||||
|
||||
# RETURN_TYPES = ('LIST', 'INT', 'INT')
|
||||
# RETURN_NAMES = ('list', 'batch index', 'batch total')
|
||||
|
||||
# FUNCTION = 'run'
|
||||
|
||||
# def run(
|
||||
# self,
|
||||
# separator: str,
|
||||
# index: int,
|
||||
# multiline: str,
|
||||
# ):
|
||||
# lines = multiline.split('\n')
|
||||
# output = []
|
||||
# for line in lines:
|
||||
# tokens = line.split(separator)
|
||||
# trimmed = []
|
||||
# for token in tokens:
|
||||
# trimmed.append(token.strip())
|
||||
# output.append(trimmed)
|
||||
|
||||
# return (output[index], index, len(output))
|
||||
|
||||
|
||||
@register_node('List: from File (backend)', 'list')
|
||||
class ListFromFile:
|
||||
def __init__(self):
|
||||
@@ -410,7 +364,7 @@ class ListRandomSeeds:
|
||||
'list of random seeds',
|
||||
TOOLTIP_OUTPUT_COUNT,
|
||||
)
|
||||
DESCRIPTION = 'Return a list of random seeds.'
|
||||
DESCRIPTION = 'Create a list of random seeds.'
|
||||
|
||||
# ensure the list is regenerated
|
||||
@classmethod
|
||||
@@ -422,3 +376,141 @@ class ListRandomSeeds:
|
||||
for i in range(0, count):
|
||||
values.append(math.floor(random.randrange(0, 0xFFFFFFFFFFFFFFFF)))
|
||||
return (values, len(values))
|
||||
|
||||
|
||||
@register_node('List: Checkpoints', 'list')
|
||||
class ListCheckpoints:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
'required': {
|
||||
'selection': (
|
||||
'SELECTION_LIST',
|
||||
{'all': folder_paths.get_filename_list('checkpoints')},
|
||||
),
|
||||
'with_extension': (
|
||||
'BOOLEAN',
|
||||
{'default': True, 'tooltip': 'keep file extension?'},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
FUNCTION = 'run'
|
||||
RETURN_TYPES = ('LIST', 'INT')
|
||||
RETURN_NAMES = ('list', 'count')
|
||||
OUTPUT_TOOLTIPS = (
|
||||
'list of checkpoint file names',
|
||||
TOOLTIP_OUTPUT_COUNT,
|
||||
)
|
||||
DESCRIPTION = 'Create a list from selected checkpoint file names.'
|
||||
|
||||
def run(self, with_extension, selection, **kwargs):
|
||||
output = []
|
||||
for file in selection['selected']:
|
||||
if not with_extension:
|
||||
output.append(str(Path(file).stem))
|
||||
else:
|
||||
output.append(file)
|
||||
return (output, len(output))
|
||||
|
||||
|
||||
@register_node('List: LoRAs', 'list')
|
||||
class ListLoras:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
'required': {
|
||||
'selection': (
|
||||
'SELECTION_LIST',
|
||||
{'all': folder_paths.get_filename_list('loras')},
|
||||
),
|
||||
'with_extension': (
|
||||
'BOOLEAN',
|
||||
{'default': True, 'tooltip': 'keep file extension?'},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
FUNCTION = 'run'
|
||||
RETURN_TYPES = ('LIST', 'INT')
|
||||
RETURN_NAMES = ('list', 'count')
|
||||
OUTPUT_TOOLTIPS = (
|
||||
'list of LoRA file names',
|
||||
TOOLTIP_OUTPUT_COUNT,
|
||||
)
|
||||
DESCRIPTION = 'Create a list from selected LoRA file names.'
|
||||
|
||||
def run(self, with_extension, selection, **kwargs):
|
||||
output = []
|
||||
for file in selection['selected']:
|
||||
if not with_extension:
|
||||
output.append(str(Path(file).stem))
|
||||
else:
|
||||
output.append(file)
|
||||
return (output, len(output))
|
||||
|
||||
|
||||
@register_node('List: Samplers', 'list')
|
||||
class ListSamplers:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
'required': {
|
||||
'selection': (
|
||||
'SELECTION_LIST',
|
||||
{'all': KSampler.SAMPLERS},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
FUNCTION = 'run'
|
||||
RETURN_TYPES = ('LIST', 'INT')
|
||||
RETURN_NAMES = ('list', 'count')
|
||||
OUTPUT_TOOLTIPS = (
|
||||
'list of sampler names',
|
||||
TOOLTIP_OUTPUT_COUNT,
|
||||
)
|
||||
DESCRIPTION = 'Create a list from selected sampler names.'
|
||||
|
||||
def run(self, selection, **kwargs):
|
||||
output = selection['selected']
|
||||
return (output, len(output))
|
||||
|
||||
|
||||
@register_node('List: Schedulers', 'list')
|
||||
class ListSchedulers:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
'required': {
|
||||
'selection': (
|
||||
'SELECTION_LIST',
|
||||
{'all': KSampler.SCHEDULERS},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
FUNCTION = 'run'
|
||||
RETURN_TYPES = ('LIST', 'INT')
|
||||
RETURN_NAMES = ('list', 'count')
|
||||
OUTPUT_TOOLTIPS = (
|
||||
'list of scheduler names',
|
||||
TOOLTIP_OUTPUT_COUNT,
|
||||
)
|
||||
DESCRIPTION = 'Create a list from selected scheduler names.'
|
||||
|
||||
def run(self, selection, **kwargs):
|
||||
output = selection['selected']
|
||||
return (output, len(output))
|
||||
|
||||
@@ -107,6 +107,16 @@ const customLabels = {
|
||||
nb_elements: 'nb of elements',
|
||||
},
|
||||
},
|
||||
ListCheckpoints: {
|
||||
widget: {
|
||||
with_extension: 'with file extension?',
|
||||
},
|
||||
},
|
||||
ListLoras: {
|
||||
widget: {
|
||||
with_extension: 'with file extension?',
|
||||
},
|
||||
},
|
||||
// CONFIG
|
||||
OutputConfigBackend: {
|
||||
widget: {
|
||||
@@ -121,31 +131,6 @@ const customLabels = {
|
||||
show_config: 'show config',
|
||||
},
|
||||
},
|
||||
OutputConfigAuto: {
|
||||
widget: {
|
||||
auto_connect: 'auto-connect?',
|
||||
multi_nodes: 'if multiple nodes found',
|
||||
error_display: 'display errors',
|
||||
show_config: 'show config',
|
||||
},
|
||||
},
|
||||
NodeConfigApplyLocal: {
|
||||
widget: {
|
||||
error_display: 'display errors',
|
||||
check_level: 'reject on',
|
||||
},
|
||||
},
|
||||
MultiConfigFetch: {
|
||||
widget: {
|
||||
node_by: 'identify nodes by',
|
||||
},
|
||||
},
|
||||
MultiConfigApplyLocal: {
|
||||
widget: {
|
||||
error_display: 'display errors',
|
||||
check_level: 'reject on',
|
||||
},
|
||||
},
|
||||
// QUEUE
|
||||
GenericQueue: {
|
||||
input: {
|
||||
@@ -270,6 +255,26 @@ const customLooks = {
|
||||
list: { shape: 'grid' },
|
||||
},
|
||||
},
|
||||
ListCheckpoints: {
|
||||
output: {
|
||||
list: { shape: 'grid' },
|
||||
},
|
||||
},
|
||||
ListLoras: {
|
||||
output: {
|
||||
list: { shape: 'grid' },
|
||||
},
|
||||
},
|
||||
ListSamplers: {
|
||||
output: {
|
||||
list: { shape: 'grid' },
|
||||
},
|
||||
},
|
||||
ListSchedulers: {
|
||||
output: {
|
||||
list: { shape: 'grid' },
|
||||
},
|
||||
},
|
||||
} as const as Record<string, CustomLook>
|
||||
|
||||
function setNodeLabels(nodeType: typeof LGraphNode, nodeData: ComfyNodeDef) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import type { LGraphNode, IWidget } from '@comfyorg/litegraph'
|
||||
|
||||
import { findWidget, loadTextFile } from '~/shared/utils.js'
|
||||
import { makeComboWidget } from '~/widgets/factories.js'
|
||||
|
||||
export function ListFromMultiline(
|
||||
nodeType: LGraphNode,
|
||||
@@ -23,3 +24,107 @@ export function ListFromMultiline(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function ListFromSelection(
|
||||
nodeType: LGraphNode,
|
||||
selection_type: string,
|
||||
) {
|
||||
const isCombo = (widget: IWidget) =>
|
||||
widget.name.startsWith(`${selection_type}_`.toLowerCase())
|
||||
|
||||
const findCombos = (node: LGraphNode) =>
|
||||
node.widgets.filter((w: IWidget) => isCombo(w))
|
||||
|
||||
const addCombo = (node: LGraphNode, selectionWidget: IWidget) => {
|
||||
const index = findCombos(node).length
|
||||
const width = node.size[0]
|
||||
const widget = makeComboWidget(
|
||||
node,
|
||||
`${selection_type}_${index}`.toLowerCase(),
|
||||
['none', ...selectionWidget.all],
|
||||
0,
|
||||
)
|
||||
// keep node width
|
||||
node.setSize([width, node.size[1]])
|
||||
|
||||
widget.label = `${selection_type} #${index + 1}`
|
||||
widget.options.serialize = false
|
||||
return widget
|
||||
}
|
||||
|
||||
const refreshSelection = (node: LGraphNode, selectionWidget: IWidget) => {
|
||||
const selected = []
|
||||
let widgetIndex = 0
|
||||
let comboIndex = 0
|
||||
while (widgetIndex < node.widgets.length) {
|
||||
const widget = node.widgets[widgetIndex]
|
||||
if (!isCombo(widget)) {
|
||||
widgetIndex += 1
|
||||
continue
|
||||
}
|
||||
if (widget.selected === 0) {
|
||||
node.widgets.splice(widgetIndex, 1)
|
||||
continue
|
||||
}
|
||||
// rename widget
|
||||
widget.name = `${selection_type}_${comboIndex}`.toLowerCase()
|
||||
widget.label = `${selection_type} #${comboIndex + 1}`
|
||||
// add to collected selected
|
||||
selected.push(widget.value)
|
||||
widgetIndex += 1
|
||||
comboIndex += 1
|
||||
}
|
||||
// add a new combo at the end
|
||||
const last = addCombo(node, selectionWidget)
|
||||
last.onSelected = () => {
|
||||
refreshSelection(node, selectionWidget)
|
||||
}
|
||||
|
||||
selectionWidget.value = { selected } // wrap into dict to avoid issues on validation
|
||||
}
|
||||
|
||||
// recreate widgets from selectionWidget
|
||||
const refreshFromSelection = (node: LGraphNode, selectionWidget: IWidget) => {
|
||||
// delete all combos widgets
|
||||
let widgetIndex = 0
|
||||
while (widgetIndex < node.widgets.length) {
|
||||
const widget = node.widgets[widgetIndex]
|
||||
if (isCombo(widget)) {
|
||||
node.widgets.splice(widgetIndex, 1)
|
||||
continue
|
||||
}
|
||||
widgetIndex += 1
|
||||
}
|
||||
// create combo widgets for selectionWidget
|
||||
const selected = selectionWidget.value.selected
|
||||
for (const value of selected) {
|
||||
const widget = addCombo(node, selectionWidget)
|
||||
widget.value = value
|
||||
// ensure widget.selected is valid
|
||||
widget.callback(widget.value)
|
||||
// only set onSelected now, to avoid having it called by prebious line
|
||||
widget.onSelected = () => {
|
||||
refreshSelection(node, selectionWidget)
|
||||
}
|
||||
}
|
||||
// add a new combo at the end
|
||||
const last = addCombo(node, selectionWidget)
|
||||
last.onSelected = () => {
|
||||
refreshSelection(node, selectionWidget)
|
||||
}
|
||||
}
|
||||
|
||||
const original_onNodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function (...args: unknown[]) {
|
||||
original_onNodeCreated?.apply(this, ...args)
|
||||
const selectionWidget = findWidget(this, 'selection')
|
||||
refreshFromSelection(this, selectionWidget)
|
||||
}
|
||||
|
||||
const original_onConfigure = nodeType.prototype.onConfigure
|
||||
nodeType.prototype.onConfigure = function (...args: unknown[]) {
|
||||
original_onConfigure?.apply(this, ...args)
|
||||
const selectionWidget = findWidget(this, 'selection')
|
||||
refreshFromSelection(this, selectionWidget)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import { CATEGORY, type GlobalThisWithCategory } from '~/shared/common.js'
|
||||
import { customizeNode } from '~/custom.js'
|
||||
import { setupDynamicConnections } from '~/shared/dynamic_connections.js'
|
||||
import { QUEUE_STATUS } from '~/widgets/queue.js'
|
||||
import { HIDDEN, BTN, ERROR_DISPLAY } from '~/widgets/misc.js'
|
||||
import { HIDDEN, BTN, ERROR_DISPLAY, SELECTION_LIST } from '~/widgets/misc.js'
|
||||
import { OutputConfigBackend, OutputConfigLocal } from '~/nodes/config.js'
|
||||
import { ListFromMultiline } from '~/nodes/list.js'
|
||||
import { ListFromMultiline, ListFromSelection } from '~/nodes/list.js'
|
||||
import { InputMultiline } from '~/nodes/input.js'
|
||||
import { DebugJSONYAML, DebugWidgetVisibility } from '~/nodes/debug.js'
|
||||
|
||||
@@ -42,6 +42,21 @@ app.registerExtension({
|
||||
start_index: 1,
|
||||
})
|
||||
break
|
||||
case 'ListFromMultiline':
|
||||
ListFromMultiline(nodeType)
|
||||
break
|
||||
case 'ListCheckpoints':
|
||||
ListFromSelection(nodeType, 'checkpoint')
|
||||
break
|
||||
case 'ListLoras':
|
||||
ListFromSelection(nodeType, 'LoRA')
|
||||
break
|
||||
case 'ListSamplers':
|
||||
ListFromSelection(nodeType, 'sampler')
|
||||
break
|
||||
case 'ListSchedulers':
|
||||
ListFromSelection(nodeType, 'scheduler')
|
||||
break
|
||||
// INPUT
|
||||
case 'InputMultiline':
|
||||
InputMultiline(nodeType)
|
||||
@@ -54,9 +69,6 @@ app.registerExtension({
|
||||
start_index: 0,
|
||||
})
|
||||
break
|
||||
case 'ListFromMultiline':
|
||||
ListFromMultiline(nodeType)
|
||||
break
|
||||
// DEBUG
|
||||
case 'DebugJSONYAML':
|
||||
DebugJSONYAML(nodeType)
|
||||
@@ -84,6 +96,7 @@ app.registerExtension({
|
||||
HIDDEN,
|
||||
BTN,
|
||||
ERROR_DISPLAY,
|
||||
SELECTION_LIST,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { LGraphNode, IWidget } from '@comfyorg/litegraph'
|
||||
import type { ComfyApp } from '@comfyorg/comfyui-frontend-types'
|
||||
|
||||
import { InputSpec } from '~/.d.ts/comfyui-frontend-types_alt.js'
|
||||
import {
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
makeComboWidget,
|
||||
} from '~/widgets/factories.js'
|
||||
import { makeWidgetAsync } from '~/shared/utils.js'
|
||||
import { log } from '~/shared/common.js'
|
||||
|
||||
/**
|
||||
* Create an hidden widget. To be used in `getCustomWidgets(app)`
|
||||
@@ -21,8 +21,8 @@ export function HIDDEN(
|
||||
node: LGraphNode,
|
||||
inputName: string,
|
||||
inputData: InputSpec,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_app: ComfyApp | undefined,
|
||||
// // eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
// _app: ComfyApp | undefined,
|
||||
) {
|
||||
// the value in inputData is only used when creating a new workdlow
|
||||
// otherwise, the cached value will be used by Comfy
|
||||
@@ -132,3 +132,20 @@ export function ERROR_DISPLAY(
|
||||
)
|
||||
return widget as ErrorDisplayWidget
|
||||
}
|
||||
|
||||
export function SELECTION_LIST(
|
||||
node: LGraphNode,
|
||||
inputName: string,
|
||||
inputData: InputSpec,
|
||||
) {
|
||||
inputData[1].value = { selected: [] } // wrap into dict to avoid issues on validation
|
||||
if (!Array.isArray(inputData[1].all) || !inputData[1].all) {
|
||||
log.error("SELECTION_LIST: 'all is invalid")
|
||||
// overwrite `all`
|
||||
inputData[1].all = []
|
||||
}
|
||||
|
||||
const widget = HIDDEN(node, inputName, inputData)
|
||||
widget.all = inputData[1].all
|
||||
return widget
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 319 KiB |
|
After Width: | Height: | Size: 128 KiB |
@@ -41,7 +41,7 @@ That being said, have a good read!
|
||||
- [XY Plot](./tutorials/XY%20Plot/)
|
||||
- [1 - the basics](./tutorials/XY%20Plot/1%20-%20the%20basics/)
|
||||
- [2 - pimp my grid](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)
|
||||
- [3 - advanced techniques](./tutorials/XY%20Plot/3%20-%20advanced%20techniques/)
|
||||
- [3 - complex variations](./tutorials/XY%20Plot/3%20-%20complex%20variations/)
|
||||
- [Format](./tutorials/Format/)
|
||||
|
||||
---
|
||||
@@ -51,3 +51,4 @@ That being said, have a good read!
|
||||
- [XY Plot](./examples/XY%20Plot/)
|
||||
- [LoRA testing](./examples/XY%20Plot/LoRA%20testing/)
|
||||
- [Epochs vs Seeds](./examples/XY%20Plot/LoRA%20testing/Epochs%20vs%20Seeds/)
|
||||
- [Various resolutions (and aspect ratios)](./examples/XY%20Plot/Various%20resolutions/)
|
||||
|
||||
@@ -7,6 +7,7 @@ Here you will find some examples demonstrating how to use the Comfy Lab XY Plot:
|
||||
- [XY Plot](./XY%20Plot/)
|
||||
- [LoRA testing](./XY%20Plot/LoRA%20testing/)
|
||||
- [Epochs vs Seeds](./XY%20Plot/LoRA%20testing/Epochs%20vs%20Seeds/)
|
||||
- [Various resolutions (and aspect ratios)](./XY%20Plot/Various%20resolutions/)
|
||||
|
||||
> [!TIP]
|
||||
> For more detailed information / explanations, please check the XY Plot [tutorials](../tutorials/XY%20Plot/) and [node references](../node%20reference/xy%20plot/).
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
# Examples / XY Plot / LoRA testing / Epochs vs Seeds
|
||||
|
||||
> [!NOTE]
|
||||
> As always, we use the following convention for node color:
|
||||
>
|
||||
> - Green: input node you surely want to customize
|
||||
> - Blue: the specific nodes we use for processing / customization
|
||||
>
|
||||
> Of course, adjust to your env / needs: checkpoint, prompt, CFG, ...
|
||||
|
||||
These examples are intended for quick re-use, feel free to check [the tutorials](../../../../tutorials/) for more detailed explanations.
|
||||
> These examples are intended for quick re-use, feel free to check [the tutorials](../../../../tutorials/) for more detailed explanations.
|
||||
|
||||
Sections:
|
||||
|
||||
@@ -18,7 +11,7 @@ Sections:
|
||||
- [Epochs vs Random seeds](#epochs-vs-random-seeds)
|
||||
|
||||
> [!TIP]
|
||||
> Here we do not customize the grid look, but it can easily be done by plugging the `OutputConfig: Grid` node to `XY Plot: Render`.
|
||||
> Here we do not customize the grid look, but it can be done easily by plugging the `Plot Config: Grid` node to `XY Plot: Render`.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -51,7 +44,7 @@ We build the LoRA model file name thanks to the `Format String` node. Here speci
|
||||
> [!NOTE]
|
||||
> More details in the [node reference](../../../../node%20reference/format.md), you can also visit [this guide](https://pyformat.info/) for different techniques applicable to Python string `format()` method
|
||||
|
||||
Finally, we pipe the resulting string to `Load LoRA`, after converting it to type Any (`*`). More explanations in the [XY Plot tutorial: 3 - advanced techniques](../../../../tutorials/XY%20Plot/3%20-%20advanced%20techniques/).
|
||||
Finally, we pipe the resulting string to `Load LoRA`, after converting it to type Any (`*`). More explanations in the [XY Plot tutorial: 3 - complex variations](../../../../tutorials/XY%20Plot/3%20-%20complex%20variations/).
|
||||
|
||||
#### Seed list
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ Here you will find some examples demonstrating how to use the Comfy Lab XY Plot:
|
||||
|
||||
- [LoRA testing](./LoRA%20testing/)
|
||||
- [Epochs vs Seeds](./LoRA%20testing/Epochs%20vs%20Seeds/)
|
||||
- [Various resolutions (and aspect ratios)](./Various%20resolutions/)
|
||||
|
||||
> [!TIP]
|
||||
> For more detailed information / explanations, please check the XY Plot [tutorials](../../tutorials/XY%20Plot/) and [node references](../../node%20reference/xy%20plot/).
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Examples / XY Plot / Various resolutions (and aspect ratios)
|
||||
|
||||
> [!NOTE]
|
||||
> These examples are intended for quick re-use, feel free to check [the tutorials](../../../tutorials/) for more detailed explanations.
|
||||
|
||||
Sections:
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Workflow](#workflow)
|
||||
|
||||
> [!TIP]
|
||||
> Here we do not customize the grid look, but it can be done easily by plugging the `Plot Config: Grid` and/or `Plot Config: Header/Footer` nodes to `XY Plot: Render`.\
|
||||
> More details in the [node reference](../../../node%20reference/xy%20plot/2%20-%20config%20nodes.md) and the [XY Plot tutorial 2 - pimp my grid](../../../tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/).
|
||||
|
||||
## Overview
|
||||
|
||||
While probably not the most useful as such, this example demonstrates the ability of the ComfyLab XY Plot to handle various resolutions or aspect ratios within the same grid.
|
||||
|
||||
Here we generate images of various resolutions, and apply rotations.
|
||||
|
||||
## Workflow
|
||||
|
||||
**Load either `various resolutions.json` or `various resolutions.png` into ComfyUI.**\
|
||||
Adjust the checkpoint to one available in your ComfyUI instance.\
|
||||
Execute the workflow.
|
||||
|
||||

|
||||
|
||||
What we did:
|
||||
|
||||
- included the ComfyLab `Resolution to Dimensions` node, to split the first input (`1024 x 1024`, ...) into width and height, piped into `Empty Latent Image`
|
||||
- we could have done this differently, I chose this method as it is straightforward in this case
|
||||
- we also used a 2nd input list to set rotations, piped into the `rotation` input of `Rotate Latent`
|
||||
- remember, we can do that because the `Queue` outputs are of type Any
|
||||
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 382 KiB After Width: | Height: | Size: 382 KiB |
@@ -32,7 +32,7 @@ And last but not least: at stated above, it can take **other types than strings:
|
||||
|
||||
### Going further
|
||||
|
||||
For more details and advanced techniques, you can check:
|
||||
For more details and complex variations, you can check:
|
||||
|
||||
- [the tutorials of the `Format` nodes](../tutorials/Format/)
|
||||
- [a guide with different techniques (padding, ...)](https://pyformat.info/)
|
||||
|
||||
@@ -4,13 +4,13 @@ All extension nodes, organized by category.
|
||||
|
||||
## XY Plot
|
||||
|
||||
| Node name | Description | Reference | Tutorial |
|
||||
| :----------------------------: | :----------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| **XY Plot: Queue** | queue dim1 / dim2 input lists into values | [core concepts](./node%20reference/xy%20plot/0%20-%20core%20concepts.md)<br/>[reference](./node%20reference/xy%20plot/1%20-%20queue%20and%20render.md) | [tutorial #1](./tutorials/XY%20Plot/1%20-%20the%20basics/)<br/>[tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)<br/>[tutorial #3](./tutorials/XY%20Plot/3%20-%20advanced%20techniques/) |
|
||||
| **XY Plot: Render** | render the XY Plot grid | [core concepts](./node%20reference/xy%20plot/0%20-%20core%20concepts.md)<br/>[reference](./node%20reference/xy%20plot/1%20-%20queue%20and%20render.md) | [tutorial #1](./tutorials/XY%20Plot/1%20-%20the%20basics/)<br/>[tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)<br/>[tutorial #3](./tutorials/XY%20Plot/3%20-%20advanced%20techniques/) |
|
||||
| **Plot Config: Grid** | configure the grid | [reference](./node%20reference/xy%20plot/2%20-%20config%20nodes.md) | [tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)<br/>[tutorial #3](./tutorials/XY%20Plot/3%20-%20advanced%20techniques/) |
|
||||
| **Plot Config: Header/Footer** | configure either page header or footer | [reference](./node%20reference/xy%20plot/2%20-%20config%20nodes.md) | [tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)<br/>[tutorial #3](./tutorials/XY%20Plot/3%20-%20advanced%20techniques/) |
|
||||
| **XY Plot: Split Data** | split XY Plot data into page index / count | [reference](./node%20reference/xy%20plot/2%20-%20config%20nodes.md) | [tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)<br/>[tutorial #3](./tutorials/XY%20Plot/3%20-%20advanced%20techniques/) |
|
||||
| Node name | Description | Reference | Tutorial |
|
||||
| :----------------------------: | :----------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| **XY Plot: Queue** | queue dim1 / dim2 input lists into values | [core concepts](./node%20reference/xy%20plot/0%20-%20core%20concepts.md)<br/>[reference](./node%20reference/xy%20plot/1%20-%20queue%20and%20render.md) | [tutorial #1](./tutorials/XY%20Plot/1%20-%20the%20basics/)<br/>[tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)<br/>[tutorial #3](./tutorials/XY%20Plot/3%20-%20complex%20variations/) |
|
||||
| **XY Plot: Render** | render the XY Plot grid | [core concepts](./node%20reference/xy%20plot/0%20-%20core%20concepts.md)<br/>[reference](./node%20reference/xy%20plot/1%20-%20queue%20and%20render.md) | [tutorial #1](./tutorials/XY%20Plot/1%20-%20the%20basics/)<br/>[tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)<br/>[tutorial #3](./tutorials/XY%20Plot/3%20-%20complex%20variations/) |
|
||||
| **Plot Config: Grid** | configure the grid | [reference](./node%20reference/xy%20plot/2%20-%20config%20nodes.md) | [tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)<br/>[tutorial #3](./tutorials/XY%20Plot/3%20-%20complex%20variations/) |
|
||||
| **Plot Config: Header/Footer** | configure either page header or footer | [reference](./node%20reference/xy%20plot/2%20-%20config%20nodes.md) | [tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)<br/>[tutorial #3](./tutorials/XY%20Plot/3%20-%20complex%20variations/) |
|
||||
| **XY Plot: Split Data** | split XY Plot data into page index / count | [reference](./node%20reference/xy%20plot/2%20-%20config%20nodes.md) | [tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)<br/>[tutorial #3](./tutorials/XY%20Plot/3%20-%20complex%20variations/) |
|
||||
|
||||
## Output Config
|
||||
|
||||
@@ -35,13 +35,17 @@ All extension nodes, organized by category.
|
||||
|
||||
| Node name | Description | Reference | Tutorial |
|
||||
| :---------------------------: | :-----------------------------------------------------------------------------------------------------------------: | :-------: | :------: |
|
||||
| **List: from String** | split a single string into elements delimited by a separator<br/>optionally convert values to integers, flotas, ... | | |
|
||||
| **List: from Multiline** | split a multiline string, each line being a value<br/>optionally convert values to integers, flotas, ... | | |
|
||||
| **List: from String** | split a single string into elements delimited by a separator<br/>optionally convert values to integers, floats, ... | | |
|
||||
| **List: from Multiline** | split a multiline string, each line being a value<br/>optionally convert values to integers, floats, ... | | |
|
||||
| **List: from File (backend)** | read a multiline text file from backend,<br/>and send its content as a list | | |
|
||||
| **List: from Elements** | create a list from any number of individual elements | | |
|
||||
| **List: Random Seeds** | create a list of random seeds | | |
|
||||
| **List: Merge** | merge any number of lists into one | | |
|
||||
| **List: Limit** | limit list to a given size | | |
|
||||
| **List: Random Seeds** | create a list of random seeds | | |
|
||||
| **List: Checkpoints** | create a list of checkpoint file names (multi-selection) | | |
|
||||
| **List: LoRAs** | create a list of LoRA file names (multi-selection) | | |
|
||||
| **List: Samplers** | create a list of sampler names (multi-selection) | | |
|
||||
| **List: Schedulers** | create a list of scheduler names (multi-selection) | | |
|
||||
|
||||
## Queue
|
||||
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
|
||||
- [1 - the basics](./XY%20Plot/1%20-%20the%20basics/)
|
||||
- [2 - pimp my grid](./XY%20Plot/2%20-%20pimp%20my%20grid/)
|
||||
- [3 - advanced techniques](./XY%20Plot/3%20-%20advanced%20techniques/)
|
||||
- [3 - complex variations](./XY%20Plot/3%20-%20complex%20variations/)
|
||||
|
||||
**[Format](./Format/)**
|
||||
|
||||
@@ -138,11 +138,12 @@ That being said, I hope you have found these tutorials interesting and easy to f
|
||||
> [!NOTE]
|
||||
> If you have any suggestion to improve them (content, features, better English, shorter sentences, ...), feel free to open [an issue](https://github.com/bugltd/ComfyLab-Pack/issues), [a discussion](https://github.com/bugltd/ComfyLab-Pack/discussions), or even a Pull Request if you can.
|
||||
|
||||
Thank you for reading, and enjoy your beautiful grids my friends!
|
||||
Thank you for reading, and enjoy your beautiful grids!
|
||||
|
||||
## Going further (advanced)
|
||||
|
||||
These 2 tutorials are intended for a large audience, and to cover the most standard use cases.\
|
||||
These 2 tutorials are intended for a large audience, and to cover the most standard use cases.
|
||||
|
||||
But please note there are **many advanced techniques you can apply with ComfyLab XY Plot nodes**, for example:
|
||||
|
||||
- vary checkpoints, LoRA (any model in fact), ... virtually anything
|
||||
@@ -153,11 +154,11 @@ But please note there are **many advanced techniques you can apply with ComfyLab
|
||||
- with the [ComfyLab `Output Config` node](../../Output%20Config/)
|
||||
- ...
|
||||
|
||||
If you want to dive deeper, I propose we move to the **next (and last) tutorial of the XY Plot series: 3 - advanced techniques**.\
|
||||
But before doing that, please note:
|
||||
If you want to go deeper, we can now move to the **next tutorial of the XY Plot series: 3 - complex variations**.\
|
||||
It will show how you can vary many things like checkpoints / LoRAs / ..., and virtually any model or value in a combo widget, by using 3 methods:
|
||||
|
||||
- it addresses some edge cases that may not be needed for most
|
||||
- it is not as detailed as those 2 first tutorials, we'll go faster
|
||||
- if you haven't done it yet, I strongly advise you read the [XY Plot core concepts](../../../node%20reference/xy%20plot/0%20-%20core%20concepts.md): it's not long, and you may learn some important design choices there.
|
||||
- simple, with dedicated nodes for checkpoints / LoRAs / samplers / schedulers
|
||||
- versatile, that should adapt to any kind of model (or combo widget)
|
||||
- with the [`Output Config` node](../../Output%20Config/), to standardize your workflow and keep parameters separated
|
||||
|
||||
Interested? Let's go to the next tutorial: **[3 - advanced techniques](../3%20-%20advanced%20techniques/)**
|
||||
Interested? Let's go to the next tutorial: **[3 - complex variations](../3%20-%20complex%20variations/)**
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
# Tutorials / XY Plot: 3 - advanced techniques
|
||||
|
||||
Welcome to this third (and last) tutorial of the XY Plot series!
|
||||
|
||||
Here we are going to see a few advanced techniques you may find useful.\
|
||||
Of course, I cannot anticipate all the use cases, but by following this tutorial, I hope you will see the versatility of the ComfyLab XY Plot nodes, and understand how to adapt for your scenario.\
|
||||
Here, no frills, we'll go straight to the point, to focus of the functionalities rather than fancy grid customization.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you haven't yet, it is recommended to follow (at least read) the first 2 tutorials ([1](../1%20-%20the%20basics/) & [2](../2%20-%20pimp%20my%20grid/)), and read the [XY Plot core concepts](../../../node%20reference/xy%20plot/0%20-%20core%20concepts.md).\
|
||||
> You will probably need them in this advanced tutorial.
|
||||
|
||||
Tutorial sections:
|
||||
|
||||
- [Part 1 - Various checkpoints (or LoRAs, ...)](#part-1---various-checkpoints-or-loras-)
|
||||
- [Part 2 - Various resolutions and aspect ratios](#part-2---various-resolutions-and-aspect-ratios)
|
||||
- [Part 3 - Using the Output Config node](#part-3---using-the-output-config-node)
|
||||
- [TL;DR / Conclusion](#tldr--conclusion)
|
||||
|
||||
> [!TIP]
|
||||
> As for all nodes in this extension, you can get useful contextual information, by just **moving the mouse pointer over a node or its inputs / widgets / outputs**. This should help you understand some details, without reading the more detailed wiki pages (yet).
|
||||
|
||||
And as always, if you do not want to follow all the steps, you can jump directly to [the conclusion](#tldr--conclusion).
|
||||
|
||||
---
|
||||
|
||||
## Part 1 - Various checkpoints (or LoRAs, ...)
|
||||
|
||||
**Load either `workflow - part 1.json` or `workflow - part 1.png` into ComfyUI.**\
|
||||
**Important:**
|
||||
|
||||
- in the first input list, adjust to checkpoints that are available in your ComfyUI instance
|
||||
- do no add the `.safetensors` extension, it will be added automatically
|
||||
|
||||
Execute the workflow.
|
||||
|
||||

|
||||
|
||||
Here we build a grid with 2 different checkpoints, vs a list of predefined seeds.
|
||||
|
||||
What should be noticed:
|
||||
|
||||
<ins>Checkpoints:</ins>
|
||||
|
||||
- we have chosen not to include the `.safetensors` extension in the input
|
||||
- it would have been displayed in the row headers, making them too long
|
||||
- instead, we have used a `Format: String` node, to append `.safetensors` to the `dim1 value` output of the `Queue` node
|
||||
- but by simply doing so, we cannot pipe the text output to the `Load Checkpoint` node, why?
|
||||
- the `chkpt_name` widget does not accept any string, but a string that exists in the list
|
||||
- so to bypass this limitation we simply convert the text output to Any, thanks to the `Convert to Any` node
|
||||
- 
|
||||
|
||||
> [!NOTE]
|
||||
> You can read more about the Any output in the [XY Plot core concepts](../../../node%20reference/xy%20plot/0%20-%20core%20concepts.md), and the [2nd tutorial of the `Output Config` node](../../Output%20Config/2%20-%20more%20options)
|
||||
|
||||
<ins>Seeds:</ins>
|
||||
|
||||
- theoritically, we should have converted the list values to integers, thanks to the `convert` combo widget
|
||||
- but it also works (for seeds only AFAIK)
|
||||
|
||||
### Lessons learned
|
||||
|
||||
- combo widgets (as `chkpt_name` in `Load Checkpoint`) do not accept simple strings
|
||||
- a workaround is to use an output with type Any (`"*"`)
|
||||
- should we have included the `.safetensors` in the first input, we could have dropped the `Format: String` and `Convert to Any` node, because the type of `Queue` outputs is Any
|
||||
- but this wouldn't be great in terms of display in the grid
|
||||
- the checkpoint list is connected to `dim1`, not `dim2`, for a performance reason
|
||||
- if you want: just switch dim1 / dim2 and check the processing time _(more in the [core concepts page](../../../node%20reference/xy%20plot/0%20-%20core%20concepts.md))_
|
||||
|
||||
> [!TIP]
|
||||
> You can basically use this approach for any combo widget, in particular when loading models (LoRA, ...).\
|
||||
> But be **very cautious** about the values you set in input: by using the Any type, we bypass the standard ComfyUI type checks, so the workflow may fail if the values are incorrect.
|
||||
|
||||
### Variant: Random seeds
|
||||
|
||||
Instead of fixed seeds, if you prefer to have a new random set of seeds each time you generate the grid, just replace the `List: ftom Multiline` node with `List: Random Seeds`.
|
||||
|
||||
## Part 2 - Various resolutions and aspect ratios
|
||||
|
||||
**Load either `workflow - part 2.json` or `workflow - part 2.png` into ComfyUI.**\
|
||||
Adjust the checkpoint to one available in your ComfyUI instance.\
|
||||
Execute the workflow.
|
||||
|
||||

|
||||
|
||||
What we did:
|
||||
|
||||
- include the ComfyLab `Resolution to Dimensions` node, to split the first input (`1024 x 1024`, ...) into width and height, piped into `Empty Latent Image`
|
||||
- we could have done this differently, I chose this method as it is straightforward in this case
|
||||
- we also used a 2nd input list to set rotations, piped into the `rotation` input of `Rotate Latent`
|
||||
- remember, we can do that because the `Queue` outputs are of type Any
|
||||
|
||||
While probably not the most useful as such, this example demonstrates **the ability of the ComfyLab XY Plot to handle various resolutions or aspect ratios within the same grid**.
|
||||
|
||||
## Part 3 - Using the Output Config node
|
||||
|
||||
Here the workflow is similar to [Part1](#part-1---various-checkpoints-or-loras-), except that we use the `Output Config` node, to simplify and standardize our workflow.
|
||||
|
||||
**Load either `workflow - part 3.json` or `workflow - part 3.png` into ComfyUI.**\
|
||||
**Copy one of the `output config - part 3.*` file, adjust the checkpoints to your env, and load it into the `Output Config` node.**
|
||||
|
||||
- you should get outputs like this (more than the 4 displayed here):
|
||||
- 
|
||||
|
||||
Execute the workflow.
|
||||
|
||||

|
||||
|
||||
As expected, we have strictly the same results as in [Part 1](#part-1---various-checkpoints-or-loras-).
|
||||
|
||||
What should be noticed:
|
||||
|
||||
- the output config file is available in 3 different formats: JSON, JSON5 and YAML
|
||||
- we replaced the input lists by a single `Output Config` node
|
||||
- we also added more outputs to the config file: CFG, steps, sampler, ... quite a lot indeed
|
||||
- of course, we could have used 2 different `Output Config` nodes, one for the XY Plot itself, and another one for the other values: it's up to you to define how you want to standardize your configs
|
||||
- note how the `Output Config` node auto-detects the type from output values:
|
||||
- for example, `cfg` is written without quotes, so it's detected as a string
|
||||
- but for more combo widgets (sampler, scheduler), we have to force type to be Any (`*`)
|
||||
|
||||
> [!TIP]
|
||||
> We won't go into details here about the configuration file itself, but you can find more information in the corresponding [node reference](../../../node%20reference/output%20config.md) and the [dedicated tutorials](../../../tutorials/Output%20Config/).
|
||||
|
||||
## TL;DR / Conclusion
|
||||
|
||||
In this tutorial, we have:
|
||||
|
||||
- seen how to handle various checkpoints (or virtually any sort of model)
|
||||
- and optimized the grid display at the same time
|
||||
- demonstrated the ability of the XY Plot to include images or various dimensions / apect ratios within the same grid
|
||||
- shown how we can use the `Output Config` node, to keep a collection of configs, while standardizing our workflow
|
||||
|
||||
This tutorial is still in construction, so I will certainly add most techniques in the future, be sure to check.
|
||||
|
||||
Also, you will find [various examples](../../../examples/) in the wiki (with less explanations).
|
||||
|
||||
In the meantime, if you have any suggestion, feel free to open [a discussion](https://github.com/bugltd/ComfyLab-Pack/discussions).
|
||||
|
||||
Thank you for reading, enjoy!
|
||||
|
Before Width: | Height: | Size: 389 KiB |
@@ -0,0 +1,145 @@
|
||||
# Tutorials / XY Plot: 3 - Complex variations
|
||||
|
||||
Welcome to this third tutorial of the XY Plot series!
|
||||
|
||||
Here we are going to see a few advanced techniques, to make virtually anything vary, for example models or any value in a combo widget.\
|
||||
To achieve that, we will use 3 different methods:
|
||||
|
||||
- simple: with dedicated nodes, for checkpoints / LoRAs / samplers / schedulers
|
||||
- versatile: adaptable to any kind of model (or combo widget)
|
||||
- with the [`Output Config`](../../Output%20Config/) node: to standardize your workflow, and keep parameters separated
|
||||
|
||||
Each of these approaches will generate the same results: depending on your specifity, pick the one that matches your specific needs.
|
||||
|
||||
> [!TIP]
|
||||
> In the following, we vary checkpoints vs a fixed list of seeds.
|
||||
> If you prefer to have a new set of **random seeds** each time you generate the grid, just replace the corresponding `List: ftom Multiline` node with `List: Random Seeds`.
|
||||
|
||||
Tutorial sections:
|
||||
|
||||
- [Expected results](#expected-results)
|
||||
- [Part 1 - Simple: with a dedicated node (for checkpoints, LoRAs, samplers, schedulers)](#part-1---simple-with-a-dedicated-node-for-checkpoints-loras-samplers-schedulers)
|
||||
- [Part 2 - Versatile: any model (or combo widget)](#part-2---versatile-any-model-or-combo-widget)
|
||||
- [Part 3 - With the `Output Config` node](#part-3---with-the-output-config-node)
|
||||
- [TL;DR / Conclusion](#tldr--conclusion)
|
||||
|
||||
And as always, if you do not want to follow all the steps, you can jump directly to [the conclusion](#tldr--conclusion).
|
||||
|
||||
---
|
||||
|
||||
## Expected results
|
||||
|
||||
If you use the exact same parameters:
|
||||
|
||||
- checkpoints: `sd_xl_base_1.0`, `Juggernaut-XI-byRunDiffusion`
|
||||
- seeds: `156680208700286`, `715168739510668`, `568208062071311`
|
||||
|
||||
You should get the same result:\
|
||||

|
||||
|
||||
## Part 1 - Simple: with a dedicated node (for checkpoints, LoRAs, samplers, schedulers)
|
||||
|
||||
> [!TIP]
|
||||
> Here we use the `List: Checkpoints` node, that allows to select any number of checkpoints (from your ComfyUI `models/checkpoints` folder), and get a list of file names.\
|
||||
> Similar nodes exist for: LoRAs, samplers, schedulers.
|
||||
|
||||
**Load either `workflow - part 1.json` or `workflow - part 1.png` into ComfyUI.**\
|
||||
**Important:**
|
||||
|
||||
- in the `List: Checkpoints` node, select a few checkpoints from your env
|
||||
- in the example, I use SDXL 1.0 and Juggernaut XL v11
|
||||
- ensure the widget `with file extension?` is unchecked, as we will add the extension later
|
||||
|
||||
Execute the workflow.
|
||||
|
||||
### What should be noticed
|
||||
|
||||
> [!NOTE]
|
||||
> we have chosen to not include the `.safetensors` extension in the input: it would have been displayed in the row headers, making them too long. Of course, you can keep it, and skip the following.
|
||||
|
||||
To append the `.safetensors` extension:
|
||||
|
||||
- we use a `Format: String` node, to add the extension to the `dim1 value` output of the `Queue` node
|
||||
- but by simply doing so, we cannot pipe the text output to the `Load Checkpoint` node, why?
|
||||
- the `chkpt_name` widget does not accept any string, but a string that exists in the list
|
||||
- so to bypass this limitation we simply convert the text output to Any, thanks to the `Convert to Any` node
|
||||
- 
|
||||
|
||||
> [!NOTE]
|
||||
> You can read more about the Any output in the [XY Plot core concepts](../../../node%20reference/xy%20plot/0%20-%20core%20concepts.md), and the [2nd tutorial of the `Output Config` node](../../Output%20Config/2%20-%20more%20options/)
|
||||
|
||||
### Lessons learned
|
||||
|
||||
- combo widgets (as `chkpt_name` in `Load Checkpoint`) do not accept simple strings
|
||||
- a workaround is to use an output with type Any (`"*"`)
|
||||
- should we have included the `.safetensors` in the first input, we could have dropped the `Format: String` and `Convert to Any` nodes, because the `XY Plot: Queue` outputs are themselves of type Any
|
||||
- but this wouldn't be great in terms of display in the grid
|
||||
- the checkpoint list is connected to `dim1`, not `dim2`, for a performance reason
|
||||
- for the test: just switch dim1 / dim2 and check the processing time _(more in the [core concepts page](../../../node%20reference/xy%20plot/0%20-%20core%20concepts.md))_
|
||||
|
||||
## Part 2 - Versatile: any model (or combo widget)
|
||||
|
||||
While it is usually simpler to use the `List` nodes dedicated to checkpoints / LoRAs / samplers / schedulers, there are some cases where we need to use a more versatile approach:
|
||||
|
||||
- we want to vary another type of model / value from a combo widget
|
||||
- we have stored the list as a separate text file (e.g. test case), and prefer to re-use it, rather than manually selecting the values
|
||||
|
||||
This short part will cover exactly that, to show how you can adapt the XY Plot to many specific cases.
|
||||
|
||||
**Load either `workflow - part 2.json` or `workflow - part 2.png` into ComfyUI.**\
|
||||
**Important:**
|
||||
|
||||
- in the first input list, adjust to checkpoints that are available in your ComfyUI instance
|
||||
- do no add the `.safetensors` extension, it will be added automatically
|
||||
|
||||
Execute the workflow.
|
||||
|
||||
### Lessons learned
|
||||
|
||||
By using a `List: String` or `List: Multiline`, we can basically send any value, as long as we ensure it matches the destinastion widget.\
|
||||
You can use this approach for any combo widget, in particular when loading models, or any value in a combo widget.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Be **very cautious** about the values you set in input: by using the Any type, we bypass the standard ComfyUI type checks, so the workflow may fail if the values are incorrect.
|
||||
|
||||
## Part 3 - With the `Output Config` node
|
||||
|
||||
Here we use the ComfyLab [`Output Config`](../../../node%20reference/output%20config.md) node, to keep our workflow and values separated.
|
||||
|
||||
**Load either `workflow - part 3.json` or `workflow - part 3.png` into ComfyUI.**\
|
||||
**Copy one of the `output config - part 3.*` files, adjust the checkpoints to your env, and load it into the `Output Config` node.**
|
||||
|
||||
- you should get outputs like this (more than the 4 displayed here):
|
||||
- 
|
||||
|
||||
Execute the workflow.
|
||||
|
||||
As expected, we have strictly the same results as in [Part1](#part-1---simple-with-a-dedicated-node-for-checkpoints-loras-samplers-schedulers) and [Part 2](#part-2---versatile-any-model-or-combo-widget).
|
||||
|
||||
### What should be noticed
|
||||
|
||||
- the output config file is available in 3 different formats: JSON, JSON5 and YAML
|
||||
- we have replaced the input lists by a single `Output Config` node
|
||||
- we've also added more outputs to the config file: CFG, steps, sampler, ... quite a lot indeed
|
||||
- of course, we could have used 2 different `Output Config` nodes, one for the XY Plot itself, and another one for the other values: it's up to you to define how you want to standardize your configs
|
||||
- note how the `Output Config` node auto-detects the type from output values:
|
||||
- for example, `cfg` is written without quotes, so it's detected as a float
|
||||
- but for combo widgets (sampler, scheduler), we have to force type to be Any (`*`)
|
||||
|
||||
> [!TIP]
|
||||
> We won't go into details here about the configuration file itself, but you can find more information in the corresponding [node reference](../../../node%20reference/output%20config.md) and the [dedicated tutorials](../../../tutorials/Output%20Config/).
|
||||
|
||||
## TL;DR / Conclusion
|
||||
|
||||
In this tutorial, we have covered 3 different methods to vary checkpoints (or many other things):
|
||||
|
||||
- simple one, using a dedicated node: applicable to checkpoints, LoRAs, samplers, schedulers
|
||||
- a more versatile one, that should adapt to many of your use cases: use a `List: from String` or `List: from Multiline`
|
||||
- one using the `Output Config` node, to standardize your workflow and keep the values separated
|
||||
|
||||
> [!TIP]
|
||||
> You can find [more examples](../../../examples/) in the wiki.
|
||||
|
||||
If you have any suggestion on how to improve this wiki, feel free to open [a discussion](https://github.com/bugltd/ComfyLab-Pack/discussions).
|
||||
|
||||
Thank you for reading, enjoy!
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
@@ -1,4 +1,4 @@
|
||||
# Tutorials / XY Plot / 3 - Advanced techniques / Part 3
|
||||
# Tutorials / XY Plot / 3 - Complex variations / Part 3
|
||||
# more details about the config format in the Output Config node reference and tutorials
|
||||
checkpoints:
|
||||
value:
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Tutorials / XY Plot / 3 - Advanced techniques / Part 3
|
||||
Tutorials / XY Plot / 3 - Complex variations / Part 3
|
||||
more details about the config format in the Output Config node reference and tutorials
|
||||
*/
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_node_id": 20,
|
||||
"last_link_id": 22,
|
||||
"last_node_id": 21,
|
||||
"last_link_id": 23,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
@@ -305,49 +305,6 @@
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "ListFromMultiline",
|
||||
"pos": [
|
||||
-240,
|
||||
180
|
||||
],
|
||||
"size": [
|
||||
290,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "list",
|
||||
"type": "LIST",
|
||||
"links": [
|
||||
18
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ListFromMultiline"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
true,
|
||||
"disabled",
|
||||
"# here we list seed values\n156680208700286\n715168739510668\n568208062071311"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "PreviewImage",
|
||||
@@ -460,49 +417,6 @@
|
||||
"color": "#2a363b",
|
||||
"bgcolor": "#3f5159"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "ListFromMultiline",
|
||||
"pos": [
|
||||
-430,
|
||||
-100
|
||||
],
|
||||
"size": [
|
||||
470,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "list",
|
||||
"type": "LIST",
|
||||
"links": [
|
||||
15
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ListFromMultiline"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
true,
|
||||
"disabled",
|
||||
"# here we list the checkpoints\n# do not add the '.safetensors' extension, it will be appended later\nsd_xl_base_1.0\nJuggernaut-XI-byRunDiffusion"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "XYPlotQueue",
|
||||
@@ -521,7 +435,7 @@
|
||||
{
|
||||
"name": "dim1",
|
||||
"type": "LIST",
|
||||
"link": 15,
|
||||
"link": 23,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
@@ -652,6 +566,96 @@
|
||||
],
|
||||
"color": "#2a363b",
|
||||
"bgcolor": "#3f5159"
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "ListCheckpoints",
|
||||
"pos": [
|
||||
-280,
|
||||
-110
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
126
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "list",
|
||||
"type": "LIST",
|
||||
"links": [
|
||||
23
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ListCheckpoints"
|
||||
},
|
||||
"widgets_values": [
|
||||
{
|
||||
"selected": [
|
||||
"sd_xl_base_1.0.safetensors"
|
||||
]
|
||||
},
|
||||
false,
|
||||
"sd_xl_base_1.0.safetensors",
|
||||
"none"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "ListFromMultiline",
|
||||
"pos": [
|
||||
-240,
|
||||
180
|
||||
],
|
||||
"size": [
|
||||
290,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "list",
|
||||
"type": "LIST",
|
||||
"links": [
|
||||
18
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ListFromMultiline"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
true,
|
||||
"integer",
|
||||
"# here we list seed values\n156680208700286\n715168739510668\n568208062071311"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
@@ -751,14 +755,6 @@
|
||||
0,
|
||||
"XY_PLOT_DATA"
|
||||
],
|
||||
[
|
||||
15,
|
||||
14,
|
||||
0,
|
||||
13,
|
||||
0,
|
||||
"LIST"
|
||||
],
|
||||
[
|
||||
18,
|
||||
15,
|
||||
@@ -798,21 +794,29 @@
|
||||
3,
|
||||
4,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
23,
|
||||
21,
|
||||
0,
|
||||
13,
|
||||
0,
|
||||
"LIST"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1.1000000000000003,
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
478.0620672954512,
|
||||
266.4479981606637
|
||||
564,
|
||||
126
|
||||
]
|
||||
},
|
||||
"node_versions": {
|
||||
"comfy-core": "0.3.10",
|
||||
"ComfyLab-Pack": "1985411cc05fb8b6faa361d2fc18c3d867e9bdb5"
|
||||
"ComfyLab-Pack": "4fe6425c10985b88586d861d8d5e45b62b8cd1fa"
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
|
After Width: | Height: | Size: 377 KiB |
@@ -0,0 +1,819 @@
|
||||
{
|
||||
"last_node_id": 20,
|
||||
"last_link_id": 22,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
413,
|
||||
389
|
||||
],
|
||||
"size": [
|
||||
425.27801513671875,
|
||||
180.6060791015625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
6
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
26,
|
||||
474
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
98
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "ckpt_name",
|
||||
"type": "COMBO",
|
||||
"link": 21,
|
||||
"widget": {
|
||||
"name": "ckpt_name"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
1
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
3,
|
||||
5
|
||||
],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
8
|
||||
],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"v1-5-pruned-emaonly.ckpt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1209,
|
||||
188
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 7
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
11
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1480,
|
||||
420
|
||||
],
|
||||
"size": [
|
||||
260,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 13
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
415,
|
||||
186
|
||||
],
|
||||
"size": [
|
||||
422.84503173828125,
|
||||
164.31304931640625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
863,
|
||||
186
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
474
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 4
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 6
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"link": 22,
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
7
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
440313153020244,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
473,
|
||||
609
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
106
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
1024,
|
||||
1024,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1820,
|
||||
180
|
||||
],
|
||||
"size": [
|
||||
440,
|
||||
320
|
||||
],
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 12
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "FormatString",
|
||||
"pos": [
|
||||
470,
|
||||
-100
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
78
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "arg0",
|
||||
"type": "STRING,INT,FLOAT,BOOLEAN",
|
||||
"link": 19,
|
||||
"label": "arg0"
|
||||
},
|
||||
{
|
||||
"name": "arg1",
|
||||
"type": "STRING,INT,FLOAT,BOOLEAN",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "formatted",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
20
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FormatString"
|
||||
},
|
||||
"widgets_values": [
|
||||
"{arg0}.safetensors"
|
||||
],
|
||||
"color": "#2a363b",
|
||||
"bgcolor": "#3f5159"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "ConvertToAny",
|
||||
"pos": [
|
||||
730,
|
||||
-100
|
||||
],
|
||||
"size": [
|
||||
168,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "*",
|
||||
"link": 20
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "value_any",
|
||||
"type": "*",
|
||||
"links": [
|
||||
21
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "value (Any)"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ConvertToAny"
|
||||
},
|
||||
"widgets_values": [],
|
||||
"color": "#2a363b",
|
||||
"bgcolor": "#3f5159"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "XYPlotQueue",
|
||||
"pos": [
|
||||
110,
|
||||
-120
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
146
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "dim1",
|
||||
"type": "LIST",
|
||||
"link": 15,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "dim2",
|
||||
"type": "LIST",
|
||||
"link": 18,
|
||||
"shape": 6,
|
||||
"label": "dim2 (opt)"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "xy_plot_data",
|
||||
"type": "XY_PLOT_DATA",
|
||||
"links": [
|
||||
14
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "XY plot data",
|
||||
"color_off": "#52395B",
|
||||
"color_on": "#9521c3"
|
||||
},
|
||||
{
|
||||
"name": "dim1_value",
|
||||
"type": "*",
|
||||
"links": [
|
||||
19
|
||||
],
|
||||
"slot_index": 1,
|
||||
"label": "dim1 value"
|
||||
},
|
||||
{
|
||||
"name": "dim2_value",
|
||||
"type": "*",
|
||||
"links": [
|
||||
22
|
||||
],
|
||||
"slot_index": 2,
|
||||
"label": "dim2 value"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "XYPlotQueue"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"color": "#2a363b",
|
||||
"bgcolor": "#3f5159"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "XYPlotRender",
|
||||
"pos": [
|
||||
1450,
|
||||
170
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
186
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "xy_plot_data",
|
||||
"type": "XY_PLOT_DATA",
|
||||
"link": 14,
|
||||
"label": "XY plot data",
|
||||
"color_off": "#52395B",
|
||||
"color_on": "#9521c3"
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 11
|
||||
},
|
||||
{
|
||||
"name": "plot_config_grid",
|
||||
"type": "PLOT_CONFIG_GRID",
|
||||
"link": null,
|
||||
"shape": 7,
|
||||
"label": "config: grid (opt)"
|
||||
},
|
||||
{
|
||||
"name": "plot_config_header",
|
||||
"type": "PLOT_CONFIG_HF",
|
||||
"link": null,
|
||||
"shape": 7,
|
||||
"label": "config: header (opt)"
|
||||
},
|
||||
{
|
||||
"name": "plot_config_footer",
|
||||
"type": "PLOT_CONFIG_HF",
|
||||
"link": null,
|
||||
"shape": 7,
|
||||
"label": "config: footer (opt)"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "grid",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
12
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
13
|
||||
],
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "XYPlotRender"
|
||||
},
|
||||
"widgets_values": [
|
||||
"{dim1}",
|
||||
"{dim2}",
|
||||
true
|
||||
],
|
||||
"color": "#2a363b",
|
||||
"bgcolor": "#3f5159"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "ListFromMultiline",
|
||||
"pos": [
|
||||
-430,
|
||||
-100
|
||||
],
|
||||
"size": [
|
||||
470,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "list",
|
||||
"type": "LIST",
|
||||
"links": [
|
||||
15
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ListFromMultiline"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
true,
|
||||
"disabled",
|
||||
"# here we list the checkpoints\n# do not add the '.safetensors' extension, it will be appended later\nsd_xl_base_1.0\nJuggernaut-XI-byRunDiffusion"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "ListFromMultiline",
|
||||
"pos": [
|
||||
-240,
|
||||
180
|
||||
],
|
||||
"size": [
|
||||
290,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "list",
|
||||
"type": "LIST",
|
||||
"links": [
|
||||
18
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ListFromMultiline"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
true,
|
||||
"integer",
|
||||
"# here we list seed values\n156680208700286\n715168739510668\n568208062071311"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
4,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
2,
|
||||
5,
|
||||
0,
|
||||
3,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
3,
|
||||
4,
|
||||
1,
|
||||
6,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
4,
|
||||
6,
|
||||
0,
|
||||
3,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
5,
|
||||
4,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
6,
|
||||
7,
|
||||
0,
|
||||
3,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
7,
|
||||
3,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
8,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
11,
|
||||
8,
|
||||
0,
|
||||
12,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
12,
|
||||
12,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
13,
|
||||
12,
|
||||
1,
|
||||
11,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
14,
|
||||
13,
|
||||
0,
|
||||
12,
|
||||
0,
|
||||
"XY_PLOT_DATA"
|
||||
],
|
||||
[
|
||||
15,
|
||||
14,
|
||||
0,
|
||||
13,
|
||||
0,
|
||||
"LIST"
|
||||
],
|
||||
[
|
||||
18,
|
||||
15,
|
||||
0,
|
||||
13,
|
||||
1,
|
||||
"LIST"
|
||||
],
|
||||
[
|
||||
19,
|
||||
13,
|
||||
1,
|
||||
20,
|
||||
0,
|
||||
"STRING,INT,FLOAT,BOOLEAN"
|
||||
],
|
||||
[
|
||||
20,
|
||||
20,
|
||||
0,
|
||||
19,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
21,
|
||||
19,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"COMBO"
|
||||
],
|
||||
[
|
||||
22,
|
||||
13,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
"INT"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
544,
|
||||
216
|
||||
]
|
||||
},
|
||||
"node_versions": {
|
||||
"comfy-core": "0.3.10",
|
||||
"ComfyLab-Pack": "4fe6425c10985b88586d861d8d5e45b62b8cd1fa"
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 390 KiB |
|
Before Width: | Height: | Size: 972 KiB After Width: | Height: | Size: 972 KiB |
@@ -41,12 +41,13 @@ Contents:
|
||||
- make the grid RGBA to add a custom background inage
|
||||
- display 'page X / Y'
|
||||
|
||||
## Tutorial 3 - advanced techniques
|
||||
## Tutorial 3 - complex variations
|
||||
|
||||
**[>> Go to the tutorial](./3%20-%20advanced%20techniques/)**
|
||||
**[>> Go to the tutorial](./3%20-%20complex%20variations/)**
|
||||
|
||||
Contents:
|
||||
|
||||
- various checkpoints (or virtually any model, LoRA, ...)
|
||||
- integrate various image resolutions / aspect ratios in the grid
|
||||
- combine the XY Plot with the `Output Config` node
|
||||
- various checkpoints (or virtually any model, LoRA, ...), following 3 methods using:
|
||||
- dedicated nodes to manually select a list of checkpoints / LoRAs / samplers / scheduler
|
||||
- a more versatile approach for vurtually anything (models, any combo value)
|
||||
- the `Output Config` node to keep the use case data separated from your workflow
|
||||
|
||||