mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-15 16:02:32 +08:00
order widgets on group node
various fixes
This commit is contained in:
parent
19ff72db0b
commit
2e79919b5a
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"path-intellisense.mappings": {
|
||||||
|
"../": "${workspaceFolder}/web/extensions/core"
|
||||||
|
},
|
||||||
|
"[python]": {
|
||||||
|
"editor.defaultFormatter": "ms-python.autopep8"
|
||||||
|
},
|
||||||
|
"python.formatting.provider": "none"
|
||||||
|
}
|
||||||
@ -114,9 +114,9 @@ describe("group node", () => {
|
|||||||
|
|
||||||
expect(group.inputs.map((i) => i.input.name)).toEqual(["CLIPTextEncode clip", "CLIPTextEncode 2 clip"]);
|
expect(group.inputs.map((i) => i.input.name)).toEqual(["CLIPTextEncode clip", "CLIPTextEncode 2 clip"]);
|
||||||
expect(group.outputs.map((i) => i.output.name)).toEqual([
|
expect(group.outputs.map((i) => i.output.name)).toEqual([
|
||||||
|
"EmptyLatentImage LATENT",
|
||||||
"CLIPTextEncode CONDITIONING",
|
"CLIPTextEncode CONDITIONING",
|
||||||
"CLIPTextEncode 2 CONDITIONING",
|
"CLIPTextEncode 2 CONDITIONING",
|
||||||
"EmptyLatentImage LATENT",
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ckpt clip to both clip inputs on the group
|
// ckpt clip to both clip inputs on the group
|
||||||
@ -228,7 +228,7 @@ describe("group node", () => {
|
|||||||
expect(group.inputs).toHaveLength(1);
|
expect(group.inputs).toHaveLength(1);
|
||||||
expect(group.inputs[0].input.type).toEqual("CLIP");
|
expect(group.inputs[0].input.type).toEqual("CLIP");
|
||||||
|
|
||||||
expect((await graph.toPrompt()).output).toEqual(getOutput([nodes.pos.id, nodes.neg.id, nodes.empty.id]));
|
expect((await graph.toPrompt()).output).toEqual(getOutput());
|
||||||
});
|
});
|
||||||
test("it can embed reroutes as outputs", async () => {
|
test("it can embed reroutes as outputs", async () => {
|
||||||
const { ez, graph, app } = await start();
|
const { ez, graph, app } = await start();
|
||||||
@ -458,24 +458,25 @@ describe("group node", () => {
|
|||||||
group2.widgets["EmptyLatentImage width"].value = 1024;
|
group2.widgets["EmptyLatentImage width"].value = 1024;
|
||||||
group2.widgets["KSampler seed"].value = 100;
|
group2.widgets["KSampler seed"].value = 100;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
expect((await graph.toPrompt()).output).toEqual({
|
expect((await graph.toPrompt()).output).toEqual({
|
||||||
...getOutput([nodes.pos.id, nodes.neg.id, nodes.empty.id, nodes.sampler.id, nodes.decode.id, nodes.save.id], {
|
...getOutput([nodes.empty.id, nodes.pos.id, nodes.neg.id, nodes.sampler.id, nodes.decode.id, nodes.save.id], {
|
||||||
[nodes.pos.id]: { text: "hello" },
|
|
||||||
[nodes.empty.id]: { width: 256 },
|
[nodes.empty.id]: { width: 256 },
|
||||||
|
[nodes.pos.id]: { text: "hello" },
|
||||||
[nodes.sampler.id]: { seed: 1 },
|
[nodes.sampler.id]: { seed: 1 },
|
||||||
}),
|
}),
|
||||||
...getOutput(
|
...getOutput(
|
||||||
{
|
{
|
||||||
[nodes.pos.id]: `${group2.id}:0`,
|
[nodes.empty.id]: `${group2.id}:${i++}`,
|
||||||
[nodes.neg.id]: `${group2.id}:1`,
|
[nodes.pos.id]: `${group2.id}:${i++}`,
|
||||||
[nodes.empty.id]: `${group2.id}:2`,
|
[nodes.neg.id]: `${group2.id}:${i++}`,
|
||||||
[nodes.sampler.id]: `${group2.id}:3`,
|
[nodes.sampler.id]: `${group2.id}:${i++}`,
|
||||||
[nodes.decode.id]: `${group2.id}:4`,
|
[nodes.decode.id]: `${group2.id}:${i++}`,
|
||||||
[nodes.save.id]: `${group2.id}:5`,
|
[nodes.save.id]: `${group2.id}:${i++}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
[nodes.pos.id]: { text: "world" },
|
|
||||||
[nodes.empty.id]: { width: 1024 },
|
[nodes.empty.id]: { width: 1024 },
|
||||||
|
[nodes.pos.id]: { text: "world" },
|
||||||
[nodes.sampler.id]: { seed: 100 },
|
[nodes.sampler.id]: { seed: 100 },
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@ -581,4 +582,49 @@ describe("group node", () => {
|
|||||||
2: { inputs: { text: "positive" }, class_type: "CLIPTextEncode" },
|
2: { inputs: { text: "positive" }, class_type: "CLIPTextEncode" },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
test("adds widgets in node execution order", async () => {
|
||||||
|
const { ez, graph, app } = await start();
|
||||||
|
const scale = ez.LatentUpscale();
|
||||||
|
const save = ez.SaveImage();
|
||||||
|
const empty = ez.EmptyLatentImage();
|
||||||
|
const decode = ez.VAEDecode();
|
||||||
|
|
||||||
|
scale.outputs.LATENT.connectTo(decode.inputs.samples);
|
||||||
|
decode.outputs.IMAGE.connectTo(save.inputs.images);
|
||||||
|
empty.outputs.LATENT.connectTo(scale.inputs.samples);
|
||||||
|
|
||||||
|
const group = await convertToGroup(app, graph, "test", [scale, save, empty, decode]);
|
||||||
|
const widgets = group.widgets.map((w) => w.widget.name);
|
||||||
|
expect(widgets).toStrictEqual([
|
||||||
|
"EmptyLatentImage width",
|
||||||
|
"EmptyLatentImage height",
|
||||||
|
"EmptyLatentImage batch_size",
|
||||||
|
"LatentUpscale upscale_method",
|
||||||
|
"LatentUpscale width",
|
||||||
|
"LatentUpscale height",
|
||||||
|
"LatentUpscale crop",
|
||||||
|
"SaveImage filename_prefix",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
test("adds output for external links when converting to group", async () => {
|
||||||
|
const { ez, graph, app } = await start();
|
||||||
|
const img = ez.EmptyLatentImage();
|
||||||
|
let decode = ez.VAEDecode(...img.outputs);
|
||||||
|
const preview1 = ez.PreviewImage(...decode.outputs);
|
||||||
|
const preview2 = ez.PreviewImage(...decode.outputs);
|
||||||
|
|
||||||
|
const group = await convertToGroup(app, graph, "test", [img, decode, preview1]);
|
||||||
|
|
||||||
|
// Ensure we have an output connected to the 2nd preview node
|
||||||
|
expect(group.outputs.length).toBe(1);
|
||||||
|
expect(group.outputs[0].connections.length).toBe(1);
|
||||||
|
expect(group.outputs[0].connections[0].targetNode.id).toBe(preview2.id);
|
||||||
|
|
||||||
|
// Convert back and ensure bothe previews are still connected
|
||||||
|
group.menu["Convert to nodes"].call();
|
||||||
|
decode = graph.find(decode);
|
||||||
|
expect(decode.outputs[0].connections.length).toBe(2);
|
||||||
|
expect(decode.outputs[0].connections[0].targetNode.id).toBe(preview1.id);
|
||||||
|
expect(decode.outputs[0].connections[1].targetNode.id).toBe(preview2.id);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -41,11 +41,13 @@ export async function registerGroupNodes(groupNodes, source, prefix, missingNode
|
|||||||
|
|
||||||
function getOutputs(config) {
|
function getOutputs(config) {
|
||||||
const map = {};
|
const map = {};
|
||||||
for (const ext of config.external) {
|
if (config.external) {
|
||||||
if (!map[ext[0]]) {
|
for (const ext of config.external) {
|
||||||
map[ext[0]] = { [ext[1]]: true };
|
if (!map[ext[0]]) {
|
||||||
} else {
|
map[ext[0]] = { [ext[1]]: true };
|
||||||
map[ext[0]][ext[1]] = true;
|
} else {
|
||||||
|
map[ext[0]][ext[1]] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
@ -247,9 +249,20 @@ class ConvertToGroupAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async register(name) {
|
async register(name) {
|
||||||
|
// We want to generate the copied nodes in execution order so the internal node widgets show in a sensible order
|
||||||
|
const nodes = app.graph.computeExecutionOrder(false);
|
||||||
|
const selectedIds = Object.keys(app.canvas.selected_nodes);
|
||||||
|
const ordered = selectedIds
|
||||||
|
.map((id) => {
|
||||||
|
const node = app.graph.getNodeById(id);
|
||||||
|
return { index: nodes.indexOf(node), node };
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.index - b.index || a.node.id - b.node.id)
|
||||||
|
.map(({ node }) => node);
|
||||||
|
|
||||||
// Use the built in copyToClipboard function to generate the node data we need
|
// Use the built in copyToClipboard function to generate the node data we need
|
||||||
const backup = localStorage.getItem("litegrapheditor_clipboard");
|
const backup = localStorage.getItem("litegrapheditor_clipboard");
|
||||||
app.canvas.copyToClipboard();
|
app.canvas.copyToClipboard(ordered);
|
||||||
const config = JSON.parse(localStorage.getItem("litegrapheditor_clipboard"));
|
const config = JSON.parse(localStorage.getItem("litegrapheditor_clipboard"));
|
||||||
localStorage.setItem("litegrapheditor_clipboard", backup);
|
localStorage.setItem("litegrapheditor_clipboard", backup);
|
||||||
|
|
||||||
@ -261,7 +274,6 @@ class ConvertToGroupAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for external links to add extra outputs
|
// Check for external links to add extra outputs
|
||||||
const selectedIds = Object.keys(app.canvas.selected_nodes);
|
|
||||||
for (let i = 0; i < selectedIds.length; i++) {
|
for (let i = 0; i < selectedIds.length; i++) {
|
||||||
const id = selectedIds[i];
|
const id = selectedIds[i];
|
||||||
const node = app.graph.getNodeById(id);
|
const node = app.graph.getNodeById(id);
|
||||||
@ -289,7 +301,7 @@ class ConvertToGroupAction {
|
|||||||
|
|
||||||
const def = buildNodeDef(config, name, globalDefs);
|
const def = buildNodeDef(config, name, globalDefs);
|
||||||
await app.registerNodeDef("workflow/" + name, def);
|
await app.registerNodeDef("workflow/" + name, def);
|
||||||
return { config, def };
|
return { config, def, nodes: ordered };
|
||||||
}
|
}
|
||||||
|
|
||||||
findOutput(slots, link, index) {
|
findOutput(slots, link, index) {
|
||||||
@ -330,7 +342,7 @@ class ConvertToGroupAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
convert(name, config, def) {
|
convert(name, config, def, nodes) {
|
||||||
const newNode = LiteGraph.createNode("workflow/" + name);
|
const newNode = LiteGraph.createNode("workflow/" + name);
|
||||||
app.graph.add(newNode);
|
app.graph.add(newNode);
|
||||||
|
|
||||||
@ -339,8 +351,7 @@ class ConvertToGroupAction {
|
|||||||
let index = 0;
|
let index = 0;
|
||||||
const slots = def[GROUP_SLOTS];
|
const slots = def[GROUP_SLOTS];
|
||||||
newNode[GROUP_IDS] = {};
|
newNode[GROUP_IDS] = {};
|
||||||
for (const id in app.canvas.selected_nodes) {
|
for (const node of nodes) {
|
||||||
const node = app.graph.getNodeById(id);
|
|
||||||
if (left == null || node.pos[0] < left) {
|
if (left == null || node.pos[0] < left) {
|
||||||
left = node.pos[0];
|
left = node.pos[0];
|
||||||
}
|
}
|
||||||
@ -351,7 +362,7 @@ class ConvertToGroupAction {
|
|||||||
this.linkOutputs(newNode, node, slots, index++);
|
this.linkOutputs(newNode, node, slots, index++);
|
||||||
|
|
||||||
// Store the original ID so the node is reused in this session
|
// Store the original ID so the node is reused in this session
|
||||||
newNode[GROUP_IDS][node._relative_id] = id;
|
newNode[GROUP_IDS][node._relative_id] = node.id;
|
||||||
app.graph.remove(node);
|
app.graph.remove(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,10 +387,10 @@ class ConvertToGroupAction {
|
|||||||
let groupNodes = extra.groupNodes;
|
let groupNodes = extra.groupNodes;
|
||||||
if (!groupNodes) extra.groupNodes = groupNodes = {};
|
if (!groupNodes) extra.groupNodes = groupNodes = {};
|
||||||
|
|
||||||
const { config, def } = await this.register(name);
|
const { config, def, nodes } = await this.register(name);
|
||||||
groupNodes[name] = config;
|
groupNodes[name] = config;
|
||||||
|
|
||||||
return this.convert(name, config, def);
|
return this.convert(name, config, def, nodes);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -593,7 +604,9 @@ const ext = {
|
|||||||
let top;
|
let top;
|
||||||
let left;
|
let left;
|
||||||
const slots = def[GROUP_SLOTS];
|
const slots = def[GROUP_SLOTS];
|
||||||
const selectedIds = Object.keys(app.canvas.selected_nodes);
|
const selectedIds = node[GROUP_IDS]
|
||||||
|
? Object.values(node[GROUP_IDS])
|
||||||
|
: Object.keys(app.canvas.selected_nodes);
|
||||||
const newNodes = [];
|
const newNodes = [];
|
||||||
for (let i = 0; i < selectedIds.length; i++) {
|
for (let i = 0; i < selectedIds.length; i++) {
|
||||||
const id = selectedIds[i];
|
const id = selectedIds[i];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user