working prototype

This commit is contained in:
Phan Tuấn Anh
2023-09-20 13:42:45 +02:00
committed by GitHub
parent 2acf60a814
commit e1f6cc0328
6 changed files with 994 additions and 125 deletions
+1 -123
View File
@@ -2,123 +2,8 @@
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
@@ -135,9 +20,6 @@ venv.bak/
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
@@ -153,8 +35,4 @@ dmypy.json
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/
+33 -2
View File
@@ -1,2 +1,33 @@
# comfyui-auto-nodes-layout
apply better nodes layout algorithm to ComfyUI workflow
# ComfyUI auto nodes layout
a ComfyUI extension to apply better nodes layout algorithm to ComfyUI workflow (mostly for visualization purpose)
this repo is a working prototype of my proof-of-concept in comfyanonymous/ComfyUI#1547
## short intro
there's already an [1-click auto-arrange graph](https://github.com/pythongosssss/ComfyUI-Custom-Scripts#auto-arrange-graph) but it relies on default `arrange()` of `LiteGraph.js` which [positions the nodes according to level of dependencies](https://github.com/jagenjo/litegraph.js/issues/9#issuecomment-377317416), it's neat but imo the wires are very disorientated for visualization purpose
from my very limited understanding, most if not all ComfyUI workflows can be qualified as [directed acyclic graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph), so we can apply better graph drawing algorithm
credit: inspiration from this [comment](https://github.com/jagenjo/litegraph.js/issues/9#issuecomment-376413726)
disclaimer: personal preference, not always guarantee a better layout
## implementation
requirements:
- ComfyUI version later than PR comfyanonymous/ComfyUI#1273 or commit `bc76b38`
implemented algorithms:
- Dagre layout from https://github.com/dagrejs/dagre
- ELK ‘layered’ layout from https://github.com/kieler/elkjs
other possible choices (but unsatisfied to me):
- ELK layouts: https://eclipse.dev/elk/reference/algorithms.html
- Cytoscape layouts: https://blog.js.cytoscape.org/2020/05/11/layouts/#choice-of-layout
undo/redo possible with https://github.com/bmad4ever/ComfyUI-Bmad-DirtyUndoRedo
TODO:
- add UI options to change density
+5
View File
@@ -0,0 +1,5 @@
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
WEB_DIRECTORY = "./web"
__all__ = ["WEB_DIRECTORY"]
+146
View File
@@ -0,0 +1,146 @@
import { app } from "/scripts/app.js";
// https://cdn.jsdelivr.net/npm/dagre/dist/dagre.min.js
import "./dagre.min.js";
// https://cdn.jsdelivr.net/npm/elkjs/lib/elk.bundled.min.js
import "./elk.bundled.min.js";
// idk how to do named import with those
app.registerExtension({
"name": "doof.autoNodesLayout",
setup() { // Add canvas menu options
const orig = LGraphCanvas.prototype.getCanvasMenuOptions;
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
const options = orig.apply(this, arguments);
options.push(myRightClickMenu); // def below
return options;
}
}
});
const myRightClickMenu = {
"content": "📍 auto nodes layout",
"has_submenu": true,
"submenu": {
"options": [
{
"content": "LiteGraph.js default layout",
"callback": () => app.graph.arrange()
},
{
"content": "Dagre.js layout",
"callback": dagreLayout // def below
},
{
"content": "ELK.js ‘layered’ layout",
"callback": elkLayeredLayout // def below
}
]
}
};
/**
* arrange nodes using Dagre layout
* @see https://github.com/dagrejs/dagre
* @todo add options to change density
* @returns {undefined} Nothing is returned.
*/
function dagreLayout() {
detectRerouteNode(); // def below
// setup dagre
const daG = new dagre.graphlib
.Graph()
.setGraph({
"rankdir": "LR", // left to right
"ranker": "network-simplex", // values: "network-simplex", "tight-tree", "longest-path"
"nodesep": 100, // spacing in same column, can be changed
"ranksep": 200, // spacing between columns, can be changed
});
// convert litegraph to dagre
app.graph._nodes.forEach((n) => daG.setNode(
n.id.toString(),
{
"label": n.type,
"width": n.size[0],
"height": n.size[1]
}
));
app.graph.links.forEach((e) => daG.setEdge(
e.origin_id.toString(),
e.target_id.toString(),
{ "label": e.type }
));
dagre.layout(daG);
// retrieve nodes position
for (const n of app.graph._nodes) {
const nodeLaidOut = daG.node(n.id.toString());
n.pos[0] = nodeLaidOut.x;
n.pos[1] = nodeLaidOut.y;
}
return;
}
/**
* arrange nodes using ELK ‘layered’ layout
* @see https://github.com/kieler/elkjs
* @todo add options to change density
* @returns {undefined} Nothing is returned.
*/
function elkLayeredLayout() {
detectRerouteNode(); // def below
// convert litegraph to elk
const myElkNodes = app.graph._nodes.map((n) => ({
"id": n.id,
"width": n.size[0],
"height": n.size[1]
}));
const myElkEdges = app.graph.links.filter(Boolean).map((e) => ({
"id": e.id,
"sources": [ e.origin_id ],
"targets": [ e.target_id ]
}));
const myElkGraph = {
"id": "root",
"children": myElkNodes,
"edges": myElkEdges,
"layoutOptions": {
"elk.algorithm": "layered"
}
}
// setup ELK & retrieve nodes position
const elk = new ELK()
.layout(myElkGraph)
.then((val) => {
for (const nodeLaidOut of val.children) {
const n = app.graph.getNodeById(nodeLaidOut.id);
n.pos[0] = nodeLaidOut.x * 2; // add more spacing
n.pos[1] = nodeLaidOut.y * 2;
}
})
.catch(console.error);
return;
}
/**
* make a alert in case there’s any reroute node
* @returns {undefined} Nothing is returned.
*/
function detectRerouteNode() {
for (const n of app.graph._nodes) {
if (n.constructor.name === "RerouteNode") {
window.alert(
"Layout algorithms work best without Reroute nodes!\n"
+ "better remove reroute before auto-layout then re-add after"
);
break;
}
}
return;
}
+801
View File
File diff suppressed because one or more lines are too long
+8
View File
File diff suppressed because one or more lines are too long