Skip to content

Commit

Permalink
order widgets on group node
Browse files Browse the repository at this point in the history
various fixes
  • Loading branch information
pythongosssss committed Nov 14, 2023
1 parent 19ff72d commit 2e79919
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 26 deletions.
9 changes: 9 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"path-intellisense.mappings": {
"../": "${workspaceFolder}/web/extensions/core"
},
"[python]": {
"editor.defaultFormatter": "ms-python.autopep8"
},
"python.formatting.provider": "none"
}
68 changes: 57 additions & 11 deletions tests-ui/tests/groupNode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ describe("group node", () => {

expect(group.inputs.map((i) => i.input.name)).toEqual(["CLIPTextEncode clip", "CLIPTextEncode 2 clip"]);
expect(group.outputs.map((i) => i.output.name)).toEqual([
"EmptyLatentImage LATENT",
"CLIPTextEncode CONDITIONING",
"CLIPTextEncode 2 CONDITIONING",
"EmptyLatentImage LATENT",
]);

// ckpt clip to both clip inputs on the group
Expand Down Expand Up @@ -228,7 +228,7 @@ describe("group node", () => {
expect(group.inputs).toHaveLength(1);
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 () => {
const { ez, graph, app } = await start();
Expand Down Expand Up @@ -458,24 +458,25 @@ describe("group node", () => {
group2.widgets["EmptyLatentImage width"].value = 1024;
group2.widgets["KSampler seed"].value = 100;

let i = 0;
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], {
[nodes.pos.id]: { text: "hello" },
...getOutput([nodes.empty.id, nodes.pos.id, nodes.neg.id, nodes.sampler.id, nodes.decode.id, nodes.save.id], {
[nodes.empty.id]: { width: 256 },
[nodes.pos.id]: { text: "hello" },
[nodes.sampler.id]: { seed: 1 },
}),
...getOutput(
{
[nodes.pos.id]: `${group2.id}:0`,
[nodes.neg.id]: `${group2.id}:1`,
[nodes.empty.id]: `${group2.id}:2`,
[nodes.sampler.id]: `${group2.id}:3`,
[nodes.decode.id]: `${group2.id}:4`,
[nodes.save.id]: `${group2.id}:5`,
[nodes.empty.id]: `${group2.id}:${i++}`,
[nodes.pos.id]: `${group2.id}:${i++}`,
[nodes.neg.id]: `${group2.id}:${i++}`,
[nodes.sampler.id]: `${group2.id}:${i++}`,
[nodes.decode.id]: `${group2.id}:${i++}`,
[nodes.save.id]: `${group2.id}:${i++}`,
},
{
[nodes.pos.id]: { text: "world" },
[nodes.empty.id]: { width: 1024 },
[nodes.pos.id]: { text: "world" },
[nodes.sampler.id]: { seed: 100 },
}
),
Expand Down Expand Up @@ -581,4 +582,49 @@ describe("group node", () => {
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);
});
});
43 changes: 28 additions & 15 deletions web/extensions/core/groupNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ export async function registerGroupNodes(groupNodes, source, prefix, missingNode

function getOutputs(config) {
const map = {};
for (const ext of config.external) {
if (!map[ext[0]]) {
map[ext[0]] = { [ext[1]]: true };
} else {
map[ext[0]][ext[1]] = true;
if (config.external) {
for (const ext of config.external) {
if (!map[ext[0]]) {
map[ext[0]] = { [ext[1]]: true };
} else {
map[ext[0]][ext[1]] = true;
}
}
}
return map;
Expand Down Expand Up @@ -247,9 +249,20 @@ class ConvertToGroupAction {
}

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
const backup = localStorage.getItem("litegrapheditor_clipboard");
app.canvas.copyToClipboard();
app.canvas.copyToClipboard(ordered);
const config = JSON.parse(localStorage.getItem("litegrapheditor_clipboard"));
localStorage.setItem("litegrapheditor_clipboard", backup);

Expand All @@ -261,7 +274,6 @@ class ConvertToGroupAction {
}

// Check for external links to add extra outputs
const selectedIds = Object.keys(app.canvas.selected_nodes);
for (let i = 0; i < selectedIds.length; i++) {
const id = selectedIds[i];
const node = app.graph.getNodeById(id);
Expand Down Expand Up @@ -289,7 +301,7 @@ class ConvertToGroupAction {

const def = buildNodeDef(config, name, globalDefs);
await app.registerNodeDef("workflow/" + name, def);
return { config, def };
return { config, def, nodes: ordered };
}

findOutput(slots, link, index) {
Expand Down Expand Up @@ -330,7 +342,7 @@ class ConvertToGroupAction {
}
}

convert(name, config, def) {
convert(name, config, def, nodes) {
const newNode = LiteGraph.createNode("workflow/" + name);
app.graph.add(newNode);

Expand All @@ -339,8 +351,7 @@ class ConvertToGroupAction {
let index = 0;
const slots = def[GROUP_SLOTS];
newNode[GROUP_IDS] = {};
for (const id in app.canvas.selected_nodes) {
const node = app.graph.getNodeById(id);
for (const node of nodes) {
if (left == null || node.pos[0] < left) {
left = node.pos[0];
}
Expand All @@ -351,7 +362,7 @@ class ConvertToGroupAction {
this.linkOutputs(newNode, node, slots, index++);

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

Expand All @@ -376,10 +387,10 @@ class ConvertToGroupAction {
let groupNodes = extra.groupNodes;
if (!groupNodes) extra.groupNodes = groupNodes = {};

const { config, def } = await this.register(name);
const { config, def, nodes } = await this.register(name);
groupNodes[name] = config;

return this.convert(name, config, def);
return this.convert(name, config, def, nodes);
},
});
}
Expand Down Expand Up @@ -593,7 +604,9 @@ const ext = {
let top;
let left;
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 = [];
for (let i = 0; i < selectedIds.length; i++) {
const id = selectedIds[i];
Expand Down

0 comments on commit 2e79919

Please sign in to comment.