ComfyUI icon indicating copy to clipboard operation
ComfyUI copied to clipboard

Convert widget to input (and back)

Open pythongosssss opened this issue 1 year ago • 6 comments

Adds convert options to the node menu image

And once converted, convert back image

New primitive node that can be connected to one of these inputs image

When connected it will copy the current value, numbers will be given a random toggle so it can be used for seeds image

Links are restricted by type + widget configuration (e.g. you cant connect the same Primitive to seed and width, as width is restricted to steps of 64)

Primitives can be linked to multiple nodes to allow sharing of values

Values are sync'd to the source node, meaning things like LoadImage display the correct value image

For now it supports text, numbers and combos but can easily be updated to support anything else image

To quickly add a primitive widget you can double click the input (the circle, not the label)

If anyone has any feedback / could give this some testing as it was quite fiddly to get working!

pythongosssss avatar Mar 24 '23 11:03 pythongosssss

This seems quite handy so far. It took a minute to figure out how to merge the widgets.js file with my copy. Not sure if that's cause mine is newer/older, or from other things I may have merged previously. But, once I got past that it seems fine now.

The double click to add Primitives is very nice. Though I did notice that only works for inputs that were modified/converted. Seemingly, on any others it's not an option. If this could be extended to work for those as well it would be very nice.

Also, when they're double clicked to spawn and auto-connected, it would be handy if their names changed some to reflect that connection better. Such as converting steps to an input on the Sampler, then double clicking to make the Primitive for it, and maybe it pops out with "Steps (Primitive)" or something like that. Just another minor thing to save increments of time, over time. Maybe more of a personal preference too though.

Being able to dynamically shift these nodes from manual input to node inputs on everything is exceedingly more useful than the previous options. This should help reduce overlap on custom nodes being variations of what this achieves. For example, WAS and other, myself included. Have all made various versions of the KSampler already, to externalize various inputs. I have around 5-6 variations currently, mostly for testing. Lol, and with one quick ninja move, this makes those seem silly and less useful now :)

Very handy. Thanks :)

GrimblyGorn avatar Mar 24 '23 15:03 GrimblyGorn

A tiny issue is that the width of the nodes gets resized when converting an input to a widget.

comfyanonymous avatar Mar 24 '23 15:03 comfyanonymous

A tiny issue is that the width of the nodes gets resized when converting an input to a widget.

Ya that does kind of bug me too a bit. Not a functional concern so much though. On mine, much of the code block below wasn't present with his, I think.

node.onResize = function (size) {
			if (node.widgets[0].last_y == null) return;
			node.onResize = function (size) {
			
				computeSize(size);
				let y = node.widgets[0].last_y;
				let freeSpace = size[1] - y;

				// Compute the height of all non customtext widgets
				let widgetHeight = 0;
				const multi = [];
				for (let i = 0; i < node.widgets.length; i++) {
					const w = node.widgets[i];
					if (w.type === "customtext") {
						multi.push(w);
					} else {
						if (w.computeSize) {
							widgetHeight += w.computeSize()[1] + 4;
						} else {
							widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 4;
						}
					}
				}

				// See how large each text input can be
				freeSpace -= widgetHeight;
				freeSpace /= multi.length;

				if (freeSpace < MIN_SIZE) {
					// There isnt enough space for all the widgets, increase the size of the node
					freeSpace = MIN_SIZE;
					node.size[1] = y + widgetHeight + freeSpace * multi.length;
				}

				// Position each of the widgets
				for (const w of node.widgets) {
					w.y = y;
					if (w.type === "customtext") {
						y += freeSpace;
					} else if (w.computeSize) {
						y += w.computeSize()[1] + 4;
					} else {
						y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
					}
				}

				this.inputHeight = freeSpace;

				// Call original resizer handler
				if (onResize) {
					onResize.apply(this, arguments);
				}
    			}
        	}
		requestAnimationFrame(() => {
			node.onResize(node.size);
			app.graph.setDirtyCanvas(true);
		});

Seemingly this handles resizing the height but not the width. Perhaps the issue could be resolved there somehow?

GrimblyGorn avatar Mar 24 '23 15:03 GrimblyGorn

The add/remove input functions automatically resize the node, i've updated it to store the original value and restore it (unless the new size needs to be bigger)

pythongosssss avatar Mar 24 '23 16:03 pythongosssss

The double click to add Primitives is very nice. Though I did notice that only works for inputs that were modified/converted. Seemingly, on any others it's not an option.

Yeah, it'll need to be manually implemented for each of the other types for what the default node would need to be

then double clicking to make the Primitive for it, and maybe it pops out with "Steps (Primitive)"

I've updated it so the new node is just the name of the original input, I don't think it needs primitive in the name

pythongosssss avatar Mar 24 '23 16:03 pythongosssss

Yeah, it'll need to be manually implemented for each of the other types for what the default node would need to be

I'll look into manually adding those as I can later then.

I've updated it so the new node is just the name of the original input, I don't think it needs primitive in the name

Ya that's even better actually. Thanks :)

GrimblyGorn avatar Mar 24 '23 17:03 GrimblyGorn

I didn't test the previous version for this but this new version is a bit click happy on this double click action. That's handy for spawning extras quickly but they should maybe disperse in an area around the first new Primitive, or in a vertical stack possibly. Currently, they stack neatly and you could get extra stacks going all over without noticing if you weren't careful. Not sure that's really an issue so much as an OCD thing though. Also, the Primitives seem to spawn in a crunched form, which is maybe another OCD thing.

I think for clarity and quicker visual referencing the naming in the menus should be more along the lines of "Externalize CFG"/"Internalize CFG" or possibly Extract & Absorb? :)

Something like would make it easier across more types too possibly.

GrimblyGorn avatar Mar 24 '23 18:03 GrimblyGorn

This could possibly use a little timer between spawning the Primitives so they'd be more likely intentional, than accidental. Something small like 50-100 milliseconds likely could work.

The vertical stacking or whatever would still be handy for intentional extras.

GrimblyGorn avatar Mar 24 '23 19:03 GrimblyGorn

image They'll now shift by the height of the titlebar if another node exists at the target position Throttled double click to 300ms, less than that didnt feel like enough and still created "accidental" nodes

pythongosssss avatar Mar 24 '23 20:03 pythongosssss

This behaves much more reasonably now. On mine, it's stacking correctly but the starting alignment has it overlapping the input edge for the node in question as per the example image below. 2023-03-24 15_41_02-ComfyUI That could be something to do with the browser I'm using too I suppose. I haven't looked at the code for this much yet to see which it might be.

Also, just to show a visual example of the re-naming I mentioned above here's this too: 2023-03-24 15_40_28-ComfyUI It could be other things like I mentioned above but I think this gives it quicker navigation. Basically then, you can mentally say E is Out and A is In. Then each time after, just go to E or A and pick your option. Gives it a tree structure of sorts.

GrimblyGorn avatar Mar 24 '23 20:03 GrimblyGorn

Now that I've played with this some more, it seems like it would be useful to have the initial Convert to Input/Extract also actually spawn a primitive when you select it. Personally, it seems more likely I wouldn't have a node already setup to make use of the new input, so having one made available is likely useful more often than not.

The double click function is still useful after, if you clone the newly extended node and want separate primitives for those connections.

Then you could spawn the node, extend it with auto-spawn primitives, clone it, and connect all those primitives, or double click to spawn new ones as needed. Seems way more fluid possibly.

GrimblyGorn avatar Mar 24 '23 23:03 GrimblyGorn

Related to this, you need to make sure node inputs accept their type, and *. It works for inputs, but not when evaluating the input where "INPUT_TYPE" != "*".

This is input for wildcard stuff, like a debug to console which will print anything to console (in my node Suite) and then allow you to use it as a pass-through as well.

WASasquatch avatar Mar 25 '23 06:03 WASasquatch