diff --git a/CHANGELOG.md b/CHANGELOG.md index eeb9af3..c58588c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 5d658ef..c73963a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/__init__.py b/__init__.py index b960cd9..2397312 100644 --- a/__init__.py +++ b/__init__.py @@ -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) ) ) diff --git a/dist/js/custom.js b/dist/js/custom.js index 5196549..30a96fb 100644 --- a/dist/js/custom.js +++ b/dist/js/custom.js @@ -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; diff --git a/dist/js/nodes/list.js b/dist/js/nodes/list.js index 603db2a..e296df7 100644 --- a/dist/js/nodes/list.js +++ b/dist/js/nodes/list.js @@ -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); + }; +} diff --git a/dist/js/register.js b/dist/js/register.js index b617f44..ae9dca5 100644 --- a/dist/js/register.js +++ b/dist/js/register.js @@ -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, }; }, }); diff --git a/dist/js/widgets/misc.js b/dist/js/widgets/misc.js index a346fbd..bbfe8b7 100644 --- a/dist/js/widgets/misc.js +++ b/dist/js/widgets/misc.js @@ -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; +} diff --git a/node_list.json b/node_list.json index dbbf286..48916f5 100644 --- a/node_list.json +++ b/node_list.json @@ -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", diff --git a/package.json b/package.json index 600befe..d87cc13 100644 --- a/package.json +++ b/package.json @@ -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'", diff --git a/pyproject.toml b/pyproject.toml index b8da262..f9c4ab1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/src/python/nodes/list.py b/src/python/nodes/list.py index 0db22d8..485e2d6 100644 --- a/src/python/nodes/list.py +++ b/src/python/nodes/list.py @@ -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)) diff --git a/src/ts/custom.ts b/src/ts/custom.ts index dc358b4..ad5e708 100644 --- a/src/ts/custom.ts +++ b/src/ts/custom.ts @@ -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 function setNodeLabels(nodeType: typeof LGraphNode, nodeData: ComfyNodeDef) { diff --git a/src/ts/nodes/list.ts b/src/ts/nodes/list.ts index 806e883..1959a0b 100644 --- a/src/ts/nodes/list.ts +++ b/src/ts/nodes/list.ts @@ -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) + } +} diff --git a/src/ts/register.ts b/src/ts/register.ts index 1129e01..ee4c80e 100644 --- a/src/ts/register.ts +++ b/src/ts/register.ts @@ -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, } }, }) diff --git a/src/ts/widgets/misc.ts b/src/ts/widgets/misc.ts index 4e529de..5188983 100644 --- a/src/ts/widgets/misc.ts +++ b/src/ts/widgets/misc.ts @@ -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 +} diff --git a/static/repo_icon.png b/static/repo_icon.png deleted file mode 100644 index 8a903fb..0000000 Binary files a/static/repo_icon.png and /dev/null differ diff --git a/static/repo_icon_400.png b/static/repo_icon_400.png new file mode 100644 index 0000000..9d915e7 Binary files /dev/null and b/static/repo_icon_400.png differ diff --git a/wiki/README.md b/wiki/README.md index 7d5d291..dd0437c 100644 --- a/wiki/README.md +++ b/wiki/README.md @@ -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/) diff --git a/wiki/examples/README.md b/wiki/examples/README.md index 9bd50f8..b083c2c 100644 --- a/wiki/examples/README.md +++ b/wiki/examples/README.md @@ -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/). diff --git a/wiki/examples/XY Plot/LoRA testing/Epochs vs Seeds/README.md b/wiki/examples/XY Plot/LoRA testing/Epochs vs Seeds/README.md index 0f27719..2c9dc12 100644 --- a/wiki/examples/XY Plot/LoRA testing/Epochs vs Seeds/README.md +++ b/wiki/examples/XY Plot/LoRA testing/Epochs vs Seeds/README.md @@ -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 diff --git a/wiki/examples/XY Plot/README.md b/wiki/examples/XY Plot/README.md index 4971cb9..4663e89 100644 --- a/wiki/examples/XY Plot/README.md +++ b/wiki/examples/XY Plot/README.md @@ -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/). diff --git a/wiki/examples/XY Plot/Various resolutions/README.md b/wiki/examples/XY Plot/Various resolutions/README.md new file mode 100644 index 0000000..69ca544 --- /dev/null +++ b/wiki/examples/XY Plot/Various resolutions/README.md @@ -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. + +![result](./images/result.jpg) + +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 diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/details/result - part 2.jpg b/wiki/examples/XY Plot/Various resolutions/images/result.jpg similarity index 100% rename from wiki/tutorials/XY Plot/3 - advanced techniques/details/result - part 2.jpg rename to wiki/examples/XY Plot/Various resolutions/images/result.jpg diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 2.json b/wiki/examples/XY Plot/Various resolutions/various resolutions.json similarity index 100% rename from wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 2.json rename to wiki/examples/XY Plot/Various resolutions/various resolutions.json diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 2.png b/wiki/examples/XY Plot/Various resolutions/various resolutions.png similarity index 100% rename from wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 2.png rename to wiki/examples/XY Plot/Various resolutions/various resolutions.png diff --git a/wiki/node reference/format.md b/wiki/node reference/format.md index 7b5e0ba..708a2c9 100644 --- a/wiki/node reference/format.md +++ b/wiki/node reference/format.md @@ -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/) diff --git a/wiki/node_list.md b/wiki/node_list.md index bfd9b76..75bb053 100644 --- a/wiki/node_list.md +++ b/wiki/node_list.md @@ -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)
[reference](./node%20reference/xy%20plot/1%20-%20queue%20and%20render.md) | [tutorial #1](./tutorials/XY%20Plot/1%20-%20the%20basics/)
[tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)
[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)
[reference](./node%20reference/xy%20plot/1%20-%20queue%20and%20render.md) | [tutorial #1](./tutorials/XY%20Plot/1%20-%20the%20basics/)
[tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)
[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/)
[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/)
[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/)
[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)
[reference](./node%20reference/xy%20plot/1%20-%20queue%20and%20render.md) | [tutorial #1](./tutorials/XY%20Plot/1%20-%20the%20basics/)
[tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)
[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)
[reference](./node%20reference/xy%20plot/1%20-%20queue%20and%20render.md) | [tutorial #1](./tutorials/XY%20Plot/1%20-%20the%20basics/)
[tutorial #2](./tutorials/XY%20Plot/2%20-%20pimp%20my%20grid/)
[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/)
[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/)
[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/)
[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
optionally convert values to integers, flotas, ... | | | -| **List: from Multiline** | split a multiline string, each line being a value
optionally convert values to integers, flotas, ... | | | +| **List: from String** | split a single string into elements delimited by a separator
optionally convert values to integers, floats, ... | | | +| **List: from Multiline** | split a multiline string, each line being a value
optionally convert values to integers, floats, ... | | | | **List: from File (backend)** | read a multiline text file from backend,
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 diff --git a/wiki/tutorials/README.md b/wiki/tutorials/README.md index e262540..42267f8 100644 --- a/wiki/tutorials/README.md +++ b/wiki/tutorials/README.md @@ -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/)** diff --git a/wiki/tutorials/XY Plot/2 - pimp my grid/README.md b/wiki/tutorials/XY Plot/2 - pimp my grid/README.md index 4834b8c..33dd6d9 100644 --- a/wiki/tutorials/XY Plot/2 - pimp my grid/README.md +++ b/wiki/tutorials/XY Plot/2 - pimp my grid/README.md @@ -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/)** diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/README.md b/wiki/tutorials/XY Plot/3 - advanced techniques/README.md deleted file mode 100644 index 089faa7..0000000 --- a/wiki/tutorials/XY Plot/3 - advanced techniques/README.md +++ /dev/null @@ -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. - -![result - part 1](./details/result%20-%20part%201.jpg) - -Here we build a grid with 2 different checkpoints, vs a list of predefined seeds. - -What should be noticed: - -Checkpoints: - -- 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 - - ![Any output](./details/detail%20-%20part%201%20-%20any.jpg) - -> [!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) - -Seeds: - -- 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. - -![result - part 2](./details/result%20-%20part%202.jpg) - -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): -- ![outputs](./details/detail%20-%20part%203%20-%20output%20config.jpg) - -Execute the workflow. - -![result - part 3](./details/result%20-%20part%201.jpg) - -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! diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 1.png b/wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 1.png deleted file mode 100644 index 50d68f5..0000000 Binary files a/wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 1.png and /dev/null differ diff --git a/wiki/tutorials/XY Plot/3 - complex variations/README.md b/wiki/tutorials/XY Plot/3 - complex variations/README.md new file mode 100644 index 0000000..0ec8163 --- /dev/null +++ b/wiki/tutorials/XY Plot/3 - complex variations/README.md @@ -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:\ +![result grid](./details/result%20.jpg) + +## 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 + - ![Any output](./details/detail%20-%20part%201-%20any.jpg) + +> [!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): +- ![outputs](./details/detail%20-%20part%203%20-%20output%20config.jpg) + +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! diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/details/detail - part 1 - any.jpg b/wiki/tutorials/XY Plot/3 - complex variations/details/detail - part 1- any.jpg similarity index 100% rename from wiki/tutorials/XY Plot/3 - advanced techniques/details/detail - part 1 - any.jpg rename to wiki/tutorials/XY Plot/3 - complex variations/details/detail - part 1- any.jpg diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/details/detail - part 3 - output config.jpg b/wiki/tutorials/XY Plot/3 - complex variations/details/detail - part 3 - output config.jpg similarity index 100% rename from wiki/tutorials/XY Plot/3 - advanced techniques/details/detail - part 3 - output config.jpg rename to wiki/tutorials/XY Plot/3 - complex variations/details/detail - part 3 - output config.jpg diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/details/result - part 1.jpg b/wiki/tutorials/XY Plot/3 - complex variations/details/result .jpg similarity index 100% rename from wiki/tutorials/XY Plot/3 - advanced techniques/details/result - part 1.jpg rename to wiki/tutorials/XY Plot/3 - complex variations/details/result .jpg diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/output config - part 3.json b/wiki/tutorials/XY Plot/3 - complex variations/output config - part 3.json similarity index 100% rename from wiki/tutorials/XY Plot/3 - advanced techniques/output config - part 3.json rename to wiki/tutorials/XY Plot/3 - complex variations/output config - part 3.json diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/output config - part 3.yaml b/wiki/tutorials/XY Plot/3 - complex variations/output config - part 3.yaml similarity index 91% rename from wiki/tutorials/XY Plot/3 - advanced techniques/output config - part 3.yaml rename to wiki/tutorials/XY Plot/3 - complex variations/output config - part 3.yaml index 8e85a9e..8201843 100644 --- a/wiki/tutorials/XY Plot/3 - advanced techniques/output config - part 3.yaml +++ b/wiki/tutorials/XY Plot/3 - complex variations/output config - part 3.yaml @@ -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: diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/output config - part3.json5 b/wiki/tutorials/XY Plot/3 - complex variations/output config - part3.json5 similarity index 92% rename from wiki/tutorials/XY Plot/3 - advanced techniques/output config - part3.json5 rename to wiki/tutorials/XY Plot/3 - complex variations/output config - part3.json5 index 0bcaf87..586c45e 100644 --- a/wiki/tutorials/XY Plot/3 - advanced techniques/output config - part3.json5 +++ b/wiki/tutorials/XY Plot/3 - complex variations/output config - part3.json5 @@ -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 */ { diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 1.json b/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 1.json similarity index 95% rename from wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 1.json rename to wiki/tutorials/XY Plot/3 - complex variations/workflow - part 1.json index c402bce..18ae8ab 100644 --- a/wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 1.json +++ b/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 1.json @@ -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 diff --git a/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 1.png b/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 1.png new file mode 100644 index 0000000..d811110 Binary files /dev/null and b/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 1.png differ diff --git a/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 2.json b/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 2.json new file mode 100644 index 0000000..71f8b0f --- /dev/null +++ b/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 2.json @@ -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 +} \ No newline at end of file diff --git a/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 2.png b/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 2.png new file mode 100644 index 0000000..e9acd5a Binary files /dev/null and b/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 2.png differ diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 3.json b/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 3.json similarity index 100% rename from wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 3.json rename to wiki/tutorials/XY Plot/3 - complex variations/workflow - part 3.json diff --git a/wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 3.png b/wiki/tutorials/XY Plot/3 - complex variations/workflow - part 3.png similarity index 100% rename from wiki/tutorials/XY Plot/3 - advanced techniques/workflow - part 3.png rename to wiki/tutorials/XY Plot/3 - complex variations/workflow - part 3.png diff --git a/wiki/tutorials/XY Plot/README.md b/wiki/tutorials/XY Plot/README.md index c2cdd04..e8b73c6 100644 --- a/wiki/tutorials/XY Plot/README.md +++ b/wiki/tutorials/XY Plot/README.md @@ -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