PROJECT INIT

This commit is contained in:
billius.ai
2024-02-11 20:00:12 +00:00
commit 14a8b1cc2b
9 changed files with 383 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
# ComfyUI Path Helper
This module provides a set of custom nodes to help with creating file paths for ComfyUI. Paths are joined using python os library. This should help issues where people are manually constructing paths with strings which then dont work for someone if they are using a different OS then them
## Features
- **Create project root folder**
- **Add Folders**
- **Add file prefix**
- **Show path/strings in a node**
- **Prefix/Postfix datetime to paths**
- **Format variables into paths**
## Nodes
#### CreateProjectRoot
#### AddFolder
#### AddFolderAdvanced
#### AddFileNamePrefix
#### AddFileNamePrefixAdvanced
#### JoinVariables
#### ShowPath
#### ShowString
## Basic usage
![basic_useage.png](imgs%2Fbasic_useage.png)
## Advanced usage
### Date Time
Advanced Nodes allow you to prefix or postfix datetime to a path
Datetime is formatted using strftime so can be customized to any format you like
more info on strftime here https://strftime.org/
![adv_datetime_use.png](imgs%2Fadv_datetime_use.png)
### Variable formatting
Advanced Nodes also allow for the formatting of variables with the node being constructed. Use the {} notion where you wish for variable to be formatted within the given string
![variable_formatting.png](imgs%2Fvariable_formatting.png)
### Multiple Variable formatting
If you wish to format multiple variables then this is also possible by passing a comma seperated string into 'input_variables'. You can use the Join Variables node to do this for you.
Note order of variables is import
![multi_variable_formatting.png](imgs%2Fmulti_variable_formatting.png)
** note input_variables will accept any type and attempt to format. I wanted to be able to pass strings, ints, floats etc but given its unrestricted this may break if given types such a LATENT
** primate node doesnt work however
## Known issues
Show Path seems to always hold first value it receives and then will redner a second text window with any updated values. Not sure why this is happening. Appears to be something related to PATH being a custom type
+16
View File
@@ -0,0 +1,16 @@
from .nodes import *
NODE_CLASS_MAPPINGS = {
"Create Project Root": CreateProjectRoot,
"Add Folder": AddFolder,
"Add Folder Advanced": AddFolderAdvanced,
"Add File Name Prefix": AddFileNamePrefix,
"Add File Name Prefix Advanced": AddFileNamePrefixAdvanced,
"Join Variables": JoinVariables,
"Show Path": ShowPath,
"Show String": ShowString,
}
WEB_DIRECTORY = "./web"
__all__ = ["NODE_CLASS_MAPPINGS", "WEB_DIRECTORY"]
print("\033[34mComfyUI Path Helper Nodes: \033[92mLoaded\033[0m")
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

+199
View File
@@ -0,0 +1,199 @@
import os
from datetime import datetime
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
ANY = AnyType("*")
def format_date_time(string, position, datetime_format):
today = datetime.now()
if position == "prefix":
return f"{today.strftime(datetime_format)}_{string}"
if position == "postfix":
return f"{string}_{today.strftime(datetime_format)}"
def format_variables(string, input_variables):
if input_variables:
variables = str(input_variables).split(",")
return string.format(*variables)
else:
return string
#
class CreateProjectRoot:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"project_root_name": ("STRING", {"multiline": False, "default": ""}),
}
}
RETURN_TYPES = ("PATH",)
FUNCTION = "create_project_root"
CATEGORY = "path_helper"
def create_project_root(self, project_root_name):
return ("./" + project_root_name,)
class AddFolder:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": ("PATH",),
"folder_name": ("STRING", {"multiline": False, "default": ""}),
}
}
RETURN_TYPES = ("PATH",)
FUNCTION = "add_folder"
CATEGORY = "path_helper"
def add_folder(self, path, folder_name):
new_path = os.path.join(path, folder_name)
return (new_path,)
class AddFolderAdvanced:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": ("PATH",),
"folder_name": ("STRING", {"multiline": False, "default": ""}),
"add_date_time": (["disable", "prefix", "postfix"],),
"date_time_format": ("STRING", {"multiline": False, "default": "%Y_%m_%d_%H:%M:%S"}),
},
"optional": {
"input_variables": (ANY,)
}
}
RETURN_TYPES = ("PATH",)
FUNCTION = "add_folder"
CATEGORY = "path_helper"
def add_folder(self, path, folder_name, add_date_time, date_time_format, input_variables=None):
folder_name_parsed = format_variables(folder_name, input_variables)
if add_date_time == "disable":
new_path = os.path.join(path, folder_name_parsed)
else:
new_path = os.path.join(path, format_date_time(folder_name_parsed, add_date_time, date_time_format))
return (new_path,)
class AddFileNamePrefix:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": ("PATH",),
"file_name_prefix": ("STRING", {"multiline": False, "default": ""}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "add_filename_prefix"
CATEGORY = "path_helper"
def add_filename_prefix(self, path, file_name_prefix):
new_path = os.path.join(path, file_name_prefix)
return (new_path,)
class AddFileNamePrefixAdvanced:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": ("PATH",),
"file_name_prefix": ("STRING", {"multiline": False, "default": ""}),
"add_date_time": (["disable", "prefix", "postfix"],),
"date_time_format": ("STRING", {"multiline": False, "default": "%Y_%m_%d_%H:%M:%S"}),
},
"optional": {
"input_variables": (ANY,)
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "add_filename_prefix"
CATEGORY = "path_helper"
def add_filename_prefix(self, path, file_name_prefix, add_date_time, date_time_format, input_variables=None):
filename_name_parsed = format_variables(file_name_prefix, input_variables)
if add_date_time == "disable":
new_path = os.path.join(path, filename_name_parsed)
else:
new_path = os.path.join(path, format_date_time(filename_name_parsed, add_date_time, date_time_format))
return (new_path,)
class JoinVariables:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"var_1": (ANY,),
},
"optional": {
"var_2": (ANY,),
"var_3": (ANY,),
"var_4": (ANY,),
}
}
FUNCTION = "join_variables"
CATEGORY = "path_helper"
RETURN_TYPES = ("STRING",)
def join_variables(self, var_1, var_2=None, var_3=None, var_4=None):
variables = [var_1, var_2, var_3, var_4]
return (','.join([str(var) for var in variables if var is not None]),)
class ShowPath:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": ("PATH",),
},
}
INPUT_IS_LIST = True
FUNCTION = "show_path"
CATEGORY = "path_helper"
OUTPUT_NODE = True
RETURN_TYPES = ("STRING",)
def show_path(self, path):
return {"ui": {"text": path}, "result": (path,)}
class ShowString:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"forceInput": True}),
},
}
INPUT_IS_LIST = True
FUNCTION = "show_string"
CATEGORY = "path_helper"
OUTPUT_NODE = True
RETURN_TYPES = ("STRING",)
def show_string(self, string):
return {"ui": {"text": string}, "result": (string,)}
+62
View File
@@ -0,0 +1,62 @@
import {app} from "../../../scripts/app.js";
import {ComfyWidgets} from "../../../scripts/widgets.js";
// Displays input path text on a node
app.registerExtension({
name: "path_helper.ShowPath",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeType.comfyClass === "Show Path") {
function populate(text) {
console.log("sp")
console.log(this.widgets)
if (this.widgets) {
for (let i = 1; i < this.widgets.length; i++) {
this.widgets[i].onRemove?.();
}
this.widgets.length = 1;
}
const v = [...text];
if (!v[0]) {
v.shift();
}
for (const list of v) {
const w = ComfyWidgets["STRING"](this, "text", ["STRING", {multiline: true}], app).widget;
w.inputEl.readOnly = true;
w.inputEl.style.opacity = 0.6;
w.value = list;
}
requestAnimationFrame(() => {
const sz = this.computeSize();
if (sz[0] < this.size[0]) {
sz[0] = this.size[0];
}
if (sz[1] < this.size[1]) {
sz[1] = this.size[1];
}
this.onResize?.(sz);
app.graph.setDirtyCanvas(true, false);
});
}
// When the node is executed we will be sent the input text, display this in the widget
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
console.log("one")
onExecuted?.apply(this, arguments);
populate.call(this, message.text);
};
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
onConfigure?.apply(this, arguments);
if (this.widgets_values?.length) {
console.log("eeee")
populate.call(this, this.widgets_values);
}
};
}
},
});
+58
View File
@@ -0,0 +1,58 @@
import {app} from "../../../scripts/app.js";
import {ComfyWidgets} from "../../../scripts/widgets.js";
app.registerExtension({
name: "path_helper.ShowString",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeType.comfyClass === "Show String") {
function populate(text) {
console.log("ss")
console.log(this.widgets)
if (this.widgets) {
for (let i = 1; i < this.widgets.length; i++) {
this.widgets[i].onRemove?.();
}
this.widgets.length = 1;
}
const v = [...text];
if (!v[0]) {
v.shift();
}
for (const list of v) {
const w = ComfyWidgets["STRING"](this, "text", ["STRING", {multiline: true}], app).widget;
w.inputEl.readOnly = true;
w.inputEl.style.opacity = 0.6;
w.value = list;
}
requestAnimationFrame(() => {
const sz = this.computeSize();
if (sz[0] < this.size[0]) {
sz[0] = this.size[0];
}
if (sz[1] < this.size[1]) {
sz[1] = this.size[1];
}
this.onResize?.(sz);
app.graph.setDirtyCanvas(true, false);
});
}
// When the node is executed we will be sent the input text, display this in the widget
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments);
populate.call(this, message.text);
};
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
onConfigure?.apply(this, arguments);
if (this.widgets_values?.length) {
populate.call(this, this.widgets_values);
}
};
}
},
});