とりあえずまとめた
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
# Use 4 spaces for the Python files
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
max_line_length = 120
|
||||
|
||||
# The JSON files contain newlines inconsistently
|
||||
[*.json]
|
||||
insert_final_newline = ignore
|
||||
|
||||
[*.js]
|
||||
indent_size = 4
|
||||
max_line_length = 120
|
||||
|
||||
# Minified JavaScript files shouldn't be changed
|
||||
[**.min.js]
|
||||
indent_style = ignore
|
||||
insert_final_newline = ignore
|
||||
|
||||
# Makefiles always use tabs for indentation
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
# Batch files use tabs for indentation
|
||||
[*.bat]
|
||||
indent_style = tab
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__pycache__
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"javascript.format.insertSpaceBeforeFunctionParenthesis": true,
|
||||
"typescript.format.insertSpaceBeforeFunctionParenthesis": true,
|
||||
"editor.tabSize": 4,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.fixAll": "explicit"
|
||||
},
|
||||
// eslint
|
||||
// 適応するファイルタイプを決定
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue"
|
||||
],
|
||||
// プロジェクト配下のeslintrc.jsを読み込み
|
||||
"eslint.options": {
|
||||
"overrideConfigFile": ".eslintrc.js"
|
||||
},
|
||||
"eslint.workingDirectories": ["./"],
|
||||
"files.associations": {
|
||||
"*.vue": "vue"
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Shingo.T
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
@author: da2el
|
||||
@title: D2 Nodes
|
||||
@description: A Collection of Handy Custom Nodes for ComfyUI
|
||||
"""
|
||||
|
||||
from .d2_nodes import NODE_CLASS_MAPPINGS
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
@@ -0,0 +1,19 @@
|
||||
size_dict:
|
||||
512 x 512:
|
||||
width: 512
|
||||
height: 512
|
||||
512 x 768:
|
||||
width: 512
|
||||
height: 768
|
||||
1024 x 1024:
|
||||
width: 1024
|
||||
height: 1024
|
||||
768 x 1024:
|
||||
width: 768
|
||||
height: 1024
|
||||
832 x 1216:
|
||||
width: 832
|
||||
height: 1216
|
||||
1248 x 1632:
|
||||
width: 1248
|
||||
height: 1632
|
||||
+480
@@ -0,0 +1,480 @@
|
||||
import torch
|
||||
import math
|
||||
import os
|
||||
import json
|
||||
import hashlib
|
||||
import folder_paths
|
||||
import comfy.sd
|
||||
import latent_preview
|
||||
import re
|
||||
import random
|
||||
|
||||
import comfy.samplers
|
||||
from comfy.cli_args import args
|
||||
|
||||
from nodes import common_ksampler, CLIPTextEncode, PreviewImage
|
||||
|
||||
from .modules import util
|
||||
from .modules import checkpoint_util
|
||||
|
||||
|
||||
MAX_SEED = 2**32 - 1 # 4,294,967,295
|
||||
|
||||
|
||||
"""
|
||||
|
||||
D2 KSampler
|
||||
positive / negative 入力に文字列が使える KSampler
|
||||
|
||||
"""
|
||||
class D2_KSampler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"model": ("MODEL",),
|
||||
"clip": ("CLIP",),
|
||||
"vae": ("VAE",),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
|
||||
"latent_image": ("LATENT",),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"preview_method": (["auto", "latent2rgb", "taesd", "vae_decoded_only", "none"],),
|
||||
"positive": ("STRING", {"default": "","multiline": True}),
|
||||
"negative": ("STRING", {"default": "", "multiline": True}),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "STRING", )
|
||||
RETURN_NAMES = ("IMAGE", "positive", "negative")
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "D2"
|
||||
|
||||
def run(self, model, clip, vae, seed, steps, cfg, sampler_name, scheduler, latent_image, denoise,
|
||||
preview_method, positive, negative, prompt=None, extra_pnginfo=None, my_unique_id=None):
|
||||
|
||||
self._set_preview_method(preview_method)
|
||||
|
||||
(positive_encoded,) = CLIPTextEncode().encode(clip, positive)
|
||||
(negative_encoded,) = CLIPTextEncode().encode(clip, negative)
|
||||
|
||||
sampler_result = common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive_encoded, negative_encoded, latent_image, denoise=denoise)
|
||||
latent = sampler_result[0]['samples']
|
||||
samp_images = vae.decode(latent).cpu()
|
||||
results_images = PreviewImage().save_images(samp_images, "d2", prompt, extra_pnginfo)['ui']['images']
|
||||
|
||||
return {
|
||||
"ui": {"images": results_images},
|
||||
"result": (samp_images, positive, negative,)
|
||||
}
|
||||
|
||||
|
||||
def _set_preview_method(self, method):
|
||||
if method == 'auto' or method == 'LatentPreviewMethod.Auto':
|
||||
args.preview_method = latent_preview.LatentPreviewMethod.Auto
|
||||
elif method == 'latent2rgb' or method == 'LatentPreviewMethod.Latent2RGB':
|
||||
args.preview_method = latent_preview.LatentPreviewMethod.Latent2RGB
|
||||
elif method == 'taesd' or method == 'LatentPreviewMethod.TAESD':
|
||||
args.preview_method = latent_preview.LatentPreviewMethod.TAESD
|
||||
else:
|
||||
args.preview_method = latent_preview.LatentPreviewMethod.NoPreviews
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
D2_CheckpointLoader
|
||||
Checkpointのフルパスを取得できる Checkpoint Loader
|
||||
|
||||
"""
|
||||
class D2_CheckpointLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (folder_paths.get_filename_list("checkpoints"), ),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO", "prompt": "PROMPT"}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP", "VAE", "STRING", "STRING", "STRING")
|
||||
RETURN_NAMES = ("model", "clip", "vae", "ckpt_name", "ckpt_hash", "ckpt_fullpath")
|
||||
FUNCTION = "load_checkpoint"
|
||||
|
||||
CATEGORY = "D2"
|
||||
|
||||
def load_checkpoint(self, ckpt_name, output_vae=True, output_clip=True, unique_id=None, extra_pnginfo=None, prompt=None):
|
||||
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
|
||||
out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
|
||||
hash = checkpoint_util.get_file_hash(ckpt_path)
|
||||
ckpt_name = os.path.basename(ckpt_name)
|
||||
return out[:3] + (ckpt_name, hash, ckpt_path)
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
D2 RegexSwitcher
|
||||
正規表現で検索して文字列を結合・出力するノード
|
||||
|
||||
"""
|
||||
class D2_RegexSwitcher:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
# 検索対象テキスト
|
||||
"text": (
|
||||
"STRING", {"forceInput": True, "multiline": True, "default": ""},
|
||||
),
|
||||
# 先頭に結合するテキスト
|
||||
"prefix": (
|
||||
"STRING", {"forceInput": True, "multiline": True, "default":"",},
|
||||
),
|
||||
# 最後に結合するテキスト
|
||||
"suffix": (
|
||||
"STRING", {"forceInput": True, "multiline": True, "default":"",},
|
||||
),
|
||||
# 正規表現、出力テキストのペア
|
||||
"regex_and_output": (
|
||||
"STRING", {"multiline": True, "default": "pony\n--\nscore_9,\n--\n--\nhighres, high quality,"},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING", "INT",)
|
||||
RETURN_NAMES = ("combined_text", "prefix", "suffix", "index",)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "D2"
|
||||
|
||||
######
|
||||
def run(self, text, prefix, suffix, regex_and_output):
|
||||
"""
|
||||
正規表現に基づいてテキストをマッチングし、結果を結合して返す関数。
|
||||
|
||||
Args:
|
||||
text (str): マッチング対象のテキスト
|
||||
pony
|
||||
--
|
||||
score_9,
|
||||
--
|
||||
--
|
||||
highres, high quality,
|
||||
prefix (str): 結果の前に付加するテキスト
|
||||
suffix (str): 結果の後に付加するテキスト
|
||||
regex_and_output (str): 正規表現とその出力のペアを "--" で区切った文字列
|
||||
|
||||
Returns:
|
||||
dict: UI用のテキストと結果のタプルを含む辞書
|
||||
"""
|
||||
# regex_and_output を -- で分割し、ペアにする
|
||||
pairs = regex_and_output.split('--')
|
||||
|
||||
# ペアをリストに整理する
|
||||
regex_output_list = []
|
||||
default_output = None
|
||||
for i in range(0, len(pairs), 2):
|
||||
if i + 1 < len(pairs):
|
||||
regex = pairs[i].strip()
|
||||
output = pairs[i+1].strip()
|
||||
if regex:
|
||||
regex_output_list.append({
|
||||
'regex': regex,
|
||||
'output': output
|
||||
})
|
||||
else:
|
||||
default_output = output
|
||||
|
||||
match_text = ""
|
||||
match_index = -1
|
||||
|
||||
# 各正規表現をチェックし、マッチしたら対応する出力を返す
|
||||
for index, item in enumerate(regex_output_list):
|
||||
if re.search(item['regex'], text, re.IGNORECASE):
|
||||
match_text = item['output']
|
||||
match_index = index
|
||||
|
||||
# マッチしなかった場合はデフォルト出力を返す
|
||||
if match_index == -1:
|
||||
match_text = default_output
|
||||
|
||||
# 文字列を結合
|
||||
combined_text = f"{prefix}{match_text}{suffix}"
|
||||
|
||||
return {
|
||||
"ui": {"text": text},
|
||||
"result": (combined_text, prefix, suffix, match_index)
|
||||
}
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
D2 PromptSR
|
||||
入力された文字列を書き換えて LIST 出力するノード
|
||||
qq-nodes-comfyui の XY Plot で使用
|
||||
|
||||
"""
|
||||
class D2_PromptSR:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
# プロンプト
|
||||
"prompt": (
|
||||
"STRING", {"multiline": True},
|
||||
),
|
||||
# 検索ワード
|
||||
"search_txt": (
|
||||
"STRING", {"multiline": False},
|
||||
),
|
||||
# 置換文字列
|
||||
"replace": (
|
||||
"STRING", {"multiline": True},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_NAMES = ("LIST",)
|
||||
FUNCTION = "replace_text"
|
||||
CATEGORY = "D2"
|
||||
|
||||
def replace_text(self, prompt, search_txt, replace):
|
||||
# 置換文字列を改行で分割
|
||||
replace_options = replace.strip().split('\n')
|
||||
|
||||
# 出力リスト
|
||||
output_list = [prompt]
|
||||
|
||||
# 文字列を検索して置換
|
||||
for option in replace_options:
|
||||
new_prompt = prompt.replace(search_txt, option)
|
||||
output_list.append(new_prompt)
|
||||
|
||||
return (output_list,)
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
D2 MultiOutput
|
||||
数値、文字列、SEEDのリストを出力するノード
|
||||
|
||||
"""
|
||||
class D2_MultiOutput:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
# 入力タイプ
|
||||
"type": (["FLOAT","INT","STRING","SEED",],),
|
||||
# プロンプト
|
||||
"parameter": (
|
||||
"STRING",{"multiline": True},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"reset": ("D2RESET", {})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_NAMES = ("LIST",)
|
||||
FUNCTION = "output_list"
|
||||
CATEGORY = "D2"
|
||||
|
||||
######
|
||||
# def output_list(self, type, parameter, seed):
|
||||
def output_list(self, type, parameter, reset = ""):
|
||||
|
||||
# 入力文字列を改行で分割
|
||||
param_options = parameter.strip().split('\n')
|
||||
|
||||
# 出力リスト
|
||||
output_list = []
|
||||
|
||||
# 文字列を検索して置換
|
||||
for option in param_options:
|
||||
if type == "INT" or type == "SEED":
|
||||
output_list.append(int(option))
|
||||
elif type == "FLOAT":
|
||||
output_list.append(float(option))
|
||||
else:
|
||||
output_list.append(option)
|
||||
|
||||
return (output_list,)
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
D2 SizeSelector
|
||||
画像サイズセレクター
|
||||
指定サイズの latent も取得できる
|
||||
|
||||
"""
|
||||
class D2_SizeSelector:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
# 設定を読む
|
||||
config_path = util.get_config_path("sizeselector_config.yaml")
|
||||
config_sample_path = util.get_config_path("sizeselector_config.sample.yaml")
|
||||
config_value = util.load_config(config_path, config_sample_path)
|
||||
|
||||
cls.size_dict = config_value["size_dict"]
|
||||
cls.size_list = ["custom"]
|
||||
cls.size_list.extend(cls.size_dict.keys())
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"preset": (cls.size_list,),
|
||||
"width": ("INT", {"default": 1024, "min": 64, "max": 8192}),
|
||||
"height": ("INT", {"default": 1024, "min": 64, "max": 8192}),
|
||||
"swap_dimensions": (["Off", "On"],),
|
||||
"upscale_factor": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 100.0, "step":0.1}),
|
||||
"prescale_factor": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 100.0, "step":0.1}),
|
||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "INT", "FLOAT", "FLOAT", "INT", "LATENT",)
|
||||
RETURN_NAMES = ("width", "height", "upscale_factor", "prescale_factor", "batch_size", "empty_latent",)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "D2"
|
||||
|
||||
def run(self, preset, width, height, swap_dimensions, upscale_factor, prescale_factor, batch_size):
|
||||
|
||||
if(preset != "custom"):
|
||||
width = self.__class__.size_dict.get(preset).get("width", width)
|
||||
height = self.__class__.size_dict.get(preset).get("height", height)
|
||||
|
||||
if swap_dimensions == "On":
|
||||
width, height = height, width
|
||||
|
||||
width = int(width*prescale_factor)
|
||||
height = int(height*prescale_factor)
|
||||
|
||||
latent = torch.zeros([batch_size, 4, height // 8, width // 8])
|
||||
|
||||
return(width, height, upscale_factor, prescale_factor, batch_size, {"samples":latent}, )
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
D2_RefinerSteps
|
||||
Refinerの切り替えステップをステップ数で指定する
|
||||
|
||||
"""
|
||||
class D2_RefinerSteps:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"steps": ("INT", {"default": 25, "min":0}),
|
||||
"start": ("INT", {"default": 0, "min":0}),
|
||||
"end": ("INT", {"default": 5, "min":0}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("INT", "INT", "INT", "INT",)
|
||||
RETURN_NAMES = ("steps", "start", "end", "refiner_start",)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "D2"
|
||||
|
||||
def run(self, steps, start, end):
|
||||
refiner_start = end + 1
|
||||
return(steps, start, end, refiner_start,)
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
D2 Refiner Steps A1111
|
||||
Refinerの切り替えステップを%で指定する
|
||||
|
||||
"""
|
||||
class D2_RefinerStepsA1111:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"steps": ("INT", {"default": 25}),
|
||||
"denoise": ("FLOAT", {"default": 1, "min":0, "max":1, "step":0.01}),
|
||||
"switch_at": ("FLOAT", {"default": 0.2, "min":0, "max":1, "step":0.01}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("INT", "INT", "INT", "INT",)
|
||||
RETURN_NAMES = ("steps", "start", "end", "refiner_start",)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "D2"
|
||||
|
||||
def run(self, steps, denoise, switch_at):
|
||||
real_steps = math.floor(steps / denoise)
|
||||
start = real_steps - steps
|
||||
end = math.floor((real_steps - start) * switch_at) + start
|
||||
refiner_start = end + 1
|
||||
|
||||
return(real_steps, start, end, refiner_start,)
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
D2 Refiner Steps Tester
|
||||
Refiner Steps の計算結果を確認するノード
|
||||
|
||||
"""
|
||||
class D2_RefinerStepsTester:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"steps": ("INT", {"forceInput":True}),
|
||||
},
|
||||
"optional": {
|
||||
"start": ("INT", {"forceInput":True}),
|
||||
"end": ("INT", {"forceInput":True}),
|
||||
"refiner_start": ("INT", {"forceInput":True}),
|
||||
}
|
||||
}
|
||||
|
||||
# INPUT_IS_LIST = True
|
||||
RETURN_TYPES = ()
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "D2"
|
||||
|
||||
def run(self, steps=0, start=0, end=0, refiner_start=0):
|
||||
text = f"stesps: {steps}\nstart: {start}\nend: {end}\nrefiner_start: {refiner_start}"
|
||||
return {"ui": {"text": text}, "result": (text,)}
|
||||
|
||||
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"D2 KSampler": D2_KSampler,
|
||||
"D2 Checkpoint Loader": D2_CheckpointLoader,
|
||||
"D2 Regex Switcher": D2_RegexSwitcher,
|
||||
"D2 Prompt SR": D2_PromptSR,
|
||||
"D2 Multi Output": D2_MultiOutput,
|
||||
"D2 Size Slector": D2_SizeSelector,
|
||||
"D2 Refiner Steps": D2_RefinerSteps,
|
||||
"D2 Refiner Steps A1111": D2_RefinerStepsA1111,
|
||||
"D2 Refiner Steps Tester": D2_RefinerStepsTester,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# ほとんどのコードは mikey_nodes のものを使っています
|
||||
# https://github.com/bash-j/mikey_nodes
|
||||
|
||||
import os
|
||||
import json
|
||||
import hashlib
|
||||
import folder_paths
|
||||
|
||||
|
||||
def calculate_file_hash(file_path):
|
||||
# open the file in binary mode
|
||||
with open(file_path, 'rb') as f:
|
||||
# read the file in chunks to avoid loading the whole file into memory
|
||||
chunk_size = 4096
|
||||
hash_object = hashlib.sha256()
|
||||
while True:
|
||||
chunk = f.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
hash_object.update(chunk)
|
||||
# return the hexadecimal representation of the hash
|
||||
return hash_object.hexdigest()
|
||||
|
||||
def get_cached_file_hashes():
|
||||
# load the cached file hashes from the JSON file
|
||||
cache_file_path = os.path.join(folder_paths.base_path, 'file_hashes.json')
|
||||
if os.path.exists(cache_file_path):
|
||||
with open(cache_file_path, 'r') as f:
|
||||
return json.load(f)
|
||||
else:
|
||||
return {}
|
||||
|
||||
def cache_file_hash(file_path, file_hash):
|
||||
# update the cached file hashes dictionary and save to the JSON file
|
||||
cache_file_path = os.path.join(folder_paths.base_path, 'file_hashes.json')
|
||||
cached_file_hashes = get_cached_file_hashes()
|
||||
cached_file_hashes[os.path.basename(file_path)] = file_hash
|
||||
with open(cache_file_path, 'w') as f:
|
||||
json.dump(cached_file_hashes, f)
|
||||
|
||||
def get_file_hash(file_path):
|
||||
# check if the file hash is already cached
|
||||
# replace \ with / in file_path
|
||||
file_path = file_path.replace('\\', '/')
|
||||
cached_file_hashes = get_cached_file_hashes()
|
||||
file_name = os.path.basename(file_path)
|
||||
if file_name in cached_file_hashes:
|
||||
return cached_file_hashes[file_name]
|
||||
else:
|
||||
# calculate the file hash and cache it
|
||||
file_hash = calculate_file_hash(file_path)[:10]
|
||||
cache_file_hash(file_path, file_hash)
|
||||
return file_hash
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import os
|
||||
import yaml
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
"""
|
||||
設定ファイルを読み込む
|
||||
ファイルがなければ見本を複製する
|
||||
"""
|
||||
def load_config(config_path:str, sample_path:str):
|
||||
if not os.path.exists(config_path):
|
||||
shutil.copy2(sample_path, config_path)
|
||||
|
||||
with open(config_path, "r", encoding="utf-8") as file:
|
||||
return yaml.safe_load(file)
|
||||
|
||||
|
||||
|
||||
"""
|
||||
ルートディレクトリ取得
|
||||
"""
|
||||
def get_root_path():
|
||||
return Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
|
||||
"""
|
||||
設定ファイルのフルパスを取得
|
||||
"""
|
||||
def get_config_path(filename):
|
||||
config_path = get_root_path() / 'config'
|
||||
return config_path / filename
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { ComfyWidgets } from "/scripts/widgets.js";
|
||||
|
||||
const BUTTON_NAME = "Add Random";
|
||||
const MAX_SEED = 2 ** 32 - 1; // 4,294,967,295
|
||||
|
||||
class D2_MultiOutputClass {
|
||||
btnWidget = undefined;
|
||||
typeWidget = undefined;
|
||||
inputWidget = undefined;
|
||||
|
||||
// seed生成ボタンの表示切り替え
|
||||
changeBtnVisible(value) {
|
||||
if (value === "SEED") {
|
||||
this.btnWidget.type = "button";
|
||||
} else {
|
||||
this.btnWidget.type = "converted-widget";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const d2mo = new D2_MultiOutputClass();
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.D2.D2_MultiOutput",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name !== "D2 Multi Output") return;
|
||||
|
||||
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const r = origOnNodeCreated ? origOnNodeCreated.apply(this) : undefined;
|
||||
|
||||
d2mo.btnWidget = this.widgets?.find((w) => w.name === BUTTON_NAME);
|
||||
d2mo.typeWidget = this.widgets?.find((w) => w.name === "type");
|
||||
d2mo.inputWidget = this.widgets?.find((w) => w.name === "parameter");
|
||||
|
||||
// seed生成ボタンの表示切り替え
|
||||
if (d2mo.typeWidget !== undefined) {
|
||||
d2mo.typeWidget.callback = (value) => {
|
||||
d2mo.changeBtnVisible(value);
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
d2mo.changeBtnVisible(d2mo.typeWidget.value);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// const r = origOnNodeCreated
|
||||
// ? origOnNodeCreated.apply(this)
|
||||
// : undefined;
|
||||
// for (const w of this.widgets) {
|
||||
// if (w.name === "seed") {
|
||||
// w.type = "converted-widget";
|
||||
// if (!w.linkedWidgets) continue;
|
||||
// for (const lw of w.linkedWidgets) {
|
||||
// lw.type = "converted-widget";
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return r;
|
||||
};
|
||||
},
|
||||
getCustomWidgets(app) {
|
||||
return {
|
||||
D2RESET(node, inputName, inputData, app) {
|
||||
const widget = node.addWidget("button", BUTTON_NAME, 0, () => {
|
||||
console.log("click reset");
|
||||
const seed = Math.floor(Math.random() * MAX_SEED);
|
||||
d2mo.inputWidget.value += d2mo.inputWidget.value.length >= 1 ? "\n" : "";
|
||||
d2mo.inputWidget.value += seed;
|
||||
});
|
||||
return widget;
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
import { ComfyWidgets } from "/scripts/widgets.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.D2.D2_RefinerStepsTester",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name !== "D2 Refiner Steps Tester") return;
|
||||
|
||||
// 以下、カスタムノード処理
|
||||
function populate(text) {
|
||||
// すでに表示しているものがあれば削除
|
||||
if (this.widgets) {
|
||||
this.widgets.forEach((widget) => {
|
||||
widget.onRemove?.();
|
||||
});
|
||||
this.widgets.length = 0;
|
||||
}
|
||||
|
||||
// 文字列はなぜか配列で送られてくるので結合
|
||||
const showText = text.join("");
|
||||
const widget = ComfyWidgets["STRING"](
|
||||
this,
|
||||
"text",
|
||||
["STRING", { multiline: true }],
|
||||
app
|
||||
).widget;
|
||||
widget.inputEl.readOnly = true;
|
||||
widget.inputEl.style.opacity = 0.6;
|
||||
widget.value = showText;
|
||||
}
|
||||
|
||||
/**
|
||||
* ノード実行時
|
||||
*/
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
populate.call(this, message.text);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
import { ComfyWidgets } from "/scripts/widgets.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.D2.D2_RegexSwitcher",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name !== "D2 Regex Switcher") return;
|
||||
|
||||
// 以下、カスタムノード処理
|
||||
function populate(text) {
|
||||
// 確認用ウィジェットがすでにあれば削除、なければ作る
|
||||
// すでに表示しているものがあれば削除
|
||||
if (this.widgets) {
|
||||
let index = this.widgets.findIndex((widget) => widget.name === "text_check");
|
||||
|
||||
if (index >= 0) {
|
||||
const widget = this.widgets[index];
|
||||
this.widgets.splice(index, 1);
|
||||
widget?.onRemove();
|
||||
}
|
||||
|
||||
const newWidget = ComfyWidgets["STRING"](
|
||||
this,
|
||||
"text_check",
|
||||
["STRING", { multiline: true }],
|
||||
app
|
||||
).widget;
|
||||
newWidget.inputEl.readOnly = true;
|
||||
newWidget.inputEl.style.opacity = 0.6;
|
||||
|
||||
// 文字列はなぜか配列で送られてくるので結合
|
||||
const showText = text.join("");
|
||||
newWidget.value = showText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ノード実行時
|
||||
*/
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
populate.call(this, message.text);
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user