order widgets on group node

various fixes
This commit is contained in:
pythongosssss 2023-11-14 19:26:54 +00:00
parent 19ff72db0b
commit 2e79919b5a
3 changed files with 94 additions and 26 deletions

9
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"path-intellisense.mappings": {
"../": "${workspaceFolder}/web/extensions/core"
},
"[python]": {
"editor.defaultFormatter": "ms-python.autopep8"
},
"python.formatting.provider": "none"
}

View File

@ -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);
});
}); });

View File

@ -41,6 +41,7 @@ export async function registerGroupNodes(groupNodes, source, prefix, missingNode
function getOutputs(config) { function getOutputs(config) {
const map = {}; const map = {};
if (config.external) {
for (const ext of config.external) { for (const ext of config.external) {
if (!map[ext[0]]) { if (!map[ext[0]]) {
map[ext[0]] = { [ext[1]]: true }; map[ext[0]] = { [ext[1]]: true };
@ -48,6 +49,7 @@ function getOutputs(config) {
map[ext[0]][ext[1]] = true; 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];