This commit is contained in:
justumen
2024-09-15 13:25:03 +02:00
parent 251012ad4a
commit 30b7f71bc3
25 changed files with 194 additions and 361 deletions

View File

@@ -1,4 +1,4 @@
# 🔗 Comfyui : Bjornulf_custom_nodes v0.29 🔗 # 🔗 Comfyui : Bjornulf_custom_nodes v0.31 🔗
# ❤️ Coffee : ☕☕☕☕☕ 5/5 # ❤️ Coffee : ☕☕☕☕☕ 5/5
@@ -77,19 +77,39 @@ wget --content-disposition -P /workspace/ComfyUI/models/checkpoints "https://civ
- **v0.27**: Two new nodes : Loop (Model+Clip+Vae) and Random (Model+Clip+Vae) - aka Checkpoint / Model - **v0.27**: Two new nodes : Loop (Model+Clip+Vae) and Random (Model+Clip+Vae) - aka Checkpoint / Model
- **v0.28**: Fix random texts and add a lot of screenshots examples for several nodes. - **v0.28**: Fix random texts and add a lot of screenshots examples for several nodes.
- **v0.29**: Fix floating points issues with loop float node. - **v0.29**: Fix floating points issues with loop float node.
- **v0.30**: Update the basic Loop node with optional input.
- **v0.31**: ❗Sorry, Breaking changes for Write/Show text nodes, cleaner system : 1 simple write text and the other is 1 advanced with console and special syntax. Also Show can now manage INT, FLOAT, TEXT.
# 📝 Nodes descriptions # 📝 Nodes descriptions
## 1/2/3 - 👁 + ✒ Show/Write Text ## 1 - 👁 Show (Text, Int, Float)
![Show Text](screenshots/write+show_text.png)
![Show Text](screenshots/write_in_console.png) ![Show Text](screenshots/show.png)
![Show Text](screenshots/write_text_select.png)
**Description:** **Description:**
Three simple nodes, two to write and one to show text. The show node will only display text, or a list of several texts. (read only node)
Write nodes is a textarea where you can write your text. 3 types are managed : Green is for STRING type, Orange is for FLOAT type and blue is for INT type.
The show text node will only display the text. (That's why I made it a different color : green, uneditable, display only.)
Write text nodes now allow for special syntax to accept random variants, like `{hood|helmet}` will randomly choose between hood or helmet. ## 2 - ✒ Write Text
![write Text](screenshots/write.png)
**Description:**
Simple node to write text.
## 3 - ✒🗔 Advanced Write Text
![write Text Advanced](screenshots/write_advanced.png)
**Description:**
Advanced Write Text node allows for special syntax to accept random variants, like `{hood|helmet}` will randomly choose between hood or helmet.
You also have `seed` and `control_after_generate` to manage the randomness.
It is also displaying the text in the comfyui console. (Useful for debugging)
Example of console logs :
```
Raw text: photo of a {green|blue|red|orange|yellow} {cat|rat|house}
Picked text: photo of a green house
```
## 4 - 🔗 Combine Texts ## 4 - 🔗 Combine Texts
![Combine Texts](screenshots/combine_texts.png) ![Combine Texts](screenshots/combine_texts.png)
@@ -102,13 +122,19 @@ Combine multiple text inputs into a single output. (can have separation with : c
**Description:** **Description:**
Generate and display random text from a predefined list. Great for creating random prompts. Generate and display random text from a predefined list. Great for creating random prompts.
You can change fixed/randomize for `control_after_generate` to have a different text each time you run the workflow. (or not) You also have `control_after_generate` to manage the randomness.
## 6 - ♻ Loop ## 6 - ♻ Loop
![Loop](screenshots/loop.png) ![Loop](screenshots/loop.png)
**Description:** **Description:**
General-purpose loop node. General-purpose loop node, you can connect that in between anything.
It has an optional input, if no input is given, it will loop over the value of the STRING "if_no_input" (take you can edit).
❗ Careful this node accept everything as input and output, so you can use it with texts, integers, images, mask, segs etc... but be consistent with your inputs/outputs.
Do not use this Loop if you can do otherwise.
This is an example together with my node 28, to force a different seed for each iteration :
![Loop](screenshots/loop4.png)
## 7 - ♻ Loop Texts ## 7 - ♻ Loop Texts
![Loop Texts](screenshots/loop_texts.png) ![Loop Texts](screenshots/loop_texts.png)
@@ -392,7 +418,7 @@ Loop over a list of images.
Usage example : You have a list of images, and you want to apply the same process to all of them. Usage example : You have a list of images, and you want to apply the same process to all of them.
Above is an example of the loop images node sending them to an Ipadapter style transfer workflow. (Same seed of course.) Above is an example of the loop images node sending them to an Ipadapter style transfer workflow. (Same seed of course.)
### 39 - ♻ Loop (✒ Write Text) ### 39 - ♻ Loop (✒🗔 Advanced Write Text)
![loop write text](screenshots/loop_write_text.png) ![loop write text](screenshots/loop_write_text.png)

View File

@@ -1,6 +1,5 @@
from .images_to_video import imagesToVideo from .images_to_video import imagesToVideo
from .write_text import WriteText from .write_text import WriteText
from .write_text_console import WriteTextInConsole
from .write_image_environment import WriteImageEnvironment from .write_image_environment import WriteImageEnvironment
from .write_image_characters import WriteImageCharacters from .write_image_characters import WriteImageCharacters
from .write_image_character import WriteImageCharacter from .write_image_character import WriteImageCharacter
@@ -17,8 +16,8 @@ from .loop_samplers import LoopSamplers
from .loop_schedulers import LoopSchedulers from .loop_schedulers import LoopSchedulers
from .ollama import ollamaLoader from .ollama import ollamaLoader
from .show_text import ShowText from .show_text import ShowText
from .show_int import ShowInt # from .show_int import ShowInt
from .show_float import ShowFloat # from .show_float import ShowFloat
from .save_text import SaveText from .save_text import SaveText
from .save_tmp_image import SaveTmpImage from .save_tmp_image import SaveTmpImage
from .save_image_path import SaveImagePath from .save_image_path import SaveImagePath
@@ -47,6 +46,8 @@ from .random_image import RandomImage
from .loop_write_text import LoopWriteText from .loop_write_text import LoopWriteText
# from .random_checkpoint import RandomCheckpoint # from .random_checkpoint import RandomCheckpoint
from .loop_model_clip_vae import LoopModelClipVae from .loop_model_clip_vae import LoopModelClipVae
from .write_text_advanced import WriteTextAdvanced
# from .show import ShowWhatever
# from .pass_preview_image import PassPreviewImage # from .pass_preview_image import PassPreviewImage
# from .check_black_image import CheckBlackImage # from .check_black_image import CheckBlackImage
@@ -57,6 +58,8 @@ from .loop_model_clip_vae import LoopModelClipVae
NODE_CLASS_MAPPINGS = { NODE_CLASS_MAPPINGS = {
# "Bjornulf_CustomStringType": CustomStringType, # "Bjornulf_CustomStringType": CustomStringType,
"Bjornulf_ollamaLoader": ollamaLoader, "Bjornulf_ollamaLoader": ollamaLoader,
"Bjornulf_WriteText": WriteText,
# "Bjornulf_ShowWhatever": ShowWhatever,
"Bjornulf_LoopModelClipVae": LoopModelClipVae, "Bjornulf_LoopModelClipVae": LoopModelClipVae,
# "Bjoenulf_RandomCheckpoint": RandomCheckpoint, # "Bjoenulf_RandomCheckpoint": RandomCheckpoint,
"Bjornulf_LoopWriteText": LoopWriteText, "Bjornulf_LoopWriteText": LoopWriteText,
@@ -78,8 +81,7 @@ NODE_CLASS_MAPPINGS = {
# "Bjornulf_CheckBlackImage": CheckBlackImage, # "Bjornulf_CheckBlackImage": CheckBlackImage,
# "Bjornulf_ClearVRAM": ClearVRAM, # "Bjornulf_ClearVRAM": ClearVRAM,
"Bjornulf_SaveBjornulfLobeChat": SaveBjornulfLobeChat, "Bjornulf_SaveBjornulfLobeChat": SaveBjornulfLobeChat,
"Bjornulf_WriteText": WriteText, "Bjornulf_WriteTextAdvanced": WriteTextAdvanced,
"Bjornulf_WriteTextInConsole": WriteTextInConsole,
"Bjornulf_RemoveTransparency": RemoveTransparency, "Bjornulf_RemoveTransparency": RemoveTransparency,
"Bjornulf_GrayscaleTransform": GrayscaleTransform, "Bjornulf_GrayscaleTransform": GrayscaleTransform,
"Bjornulf_CombineBackgroundOverlay": CombineBackgroundOverlay, "Bjornulf_CombineBackgroundOverlay": CombineBackgroundOverlay,
@@ -88,8 +90,8 @@ NODE_CLASS_MAPPINGS = {
# "Bjornulf_WriteImageCharacter": WriteImageCharacter, # "Bjornulf_WriteImageCharacter": WriteImageCharacter,
# "Bjornulf_WriteImageAllInOne": WriteImageAllInOne, # "Bjornulf_WriteImageAllInOne": WriteImageAllInOne,
"Bjornulf_ShowText": ShowText, "Bjornulf_ShowText": ShowText,
"Bjornulf_ShowInt": ShowInt, # "Bjornulf_ShowInt": ShowInt,
"Bjornulf_ShowFloat": ShowFloat, # "Bjornulf_ShowFloat": ShowFloat,
"Bjornulf_SaveText": SaveText, "Bjornulf_SaveText": SaveText,
"Bjornulf_ResizeImage": ResizeImage, "Bjornulf_ResizeImage": ResizeImage,
"Bjornulf_SaveImageToFolder": SaveImageToFolder, "Bjornulf_SaveImageToFolder": SaveImageToFolder,
@@ -111,10 +113,9 @@ NODE_CLASS_MAPPINGS = {
} }
NODE_DISPLAY_NAME_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = {
# "Bjornulf_CustomStringType": "!!! CUSTOM STRING TYPE !!!",
"Bjornulf_WriteText": "✒ Write Text", "Bjornulf_WriteText": "✒ Write Text",
"Bjornulf_WriteTextInConsole": "✒🗔 Write Text (Console too) ", "Bjornulf_WriteTextAdvanced": "✒🗔 Advanced Write Text",
"Bjornulf_LoopWriteText": "♻ Loop (✒ Write Text)", "Bjornulf_LoopWriteTextAdvanced": "♻ Loop (✒🗔 Advanced Write Text)",
"Bjornulf_LoopModelClipVae": "♻ Loop (Model+Clip+Vae)", "Bjornulf_LoopModelClipVae": "♻ Loop (Model+Clip+Vae)",
"Bjornulf_LoopImages": "♻🖼 Loop (Images)", "Bjornulf_LoopImages": "♻🖼 Loop (Images)",
"Bjornulf_CombineTextsByLines": "♻ Loop (All Lines from input 🔗 combine by lines)", "Bjornulf_CombineTextsByLines": "♻ Loop (All Lines from input 🔗 combine by lines)",
@@ -137,9 +138,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Bjornulf_SaveBjornulfLobeChat": "🖼💬 Save image for Bjornulf LobeChat", "Bjornulf_SaveBjornulfLobeChat": "🖼💬 Save image for Bjornulf LobeChat",
"Bjornulf_TextToStringAndSeed": "🔢 Text with random Seed", "Bjornulf_TextToStringAndSeed": "🔢 Text with random Seed",
# "Bjornulf_ClearVRAM": "🧹 Clear VRAM", # "Bjornulf_ClearVRAM": "🧹 Clear VRAM",
"Bjornulf_ShowText": "👁 Show (Text)", # "Bjornulf_ShowWhatever": "👁 Show Anything",
"Bjornulf_ShowInt": "👁 Show (Int)", "Bjornulf_ShowText": "👁 Show (Text, Int, Float)",
"Bjornulf_ShowFloat": "👁 Show (Float)", # "Bjornulf_ShowInt": "👁 Show (Int)",
# "Bjornulf_ShowFloat": "👁 Show (Float)",
"Bjornulf_ImageMaskCutter": "🖼✂ Cut Image with Mask", "Bjornulf_ImageMaskCutter": "🖼✂ Cut Image with Mask",
"Bjornulf_LoadImageWithTransparency": "🖼 Load Image with Transparency ▢", "Bjornulf_LoadImageWithTransparency": "🖼 Load Image with Transparency ▢",
"Bjornulf_CombineBackgroundOverlay": "🖼+🖼 Combine images (Background+Overlay alpha)", "Bjornulf_CombineBackgroundOverlay": "🖼+🖼 Combine images (Background+Overlay alpha)",

View File

@@ -1,21 +1,39 @@
class LoopBasicBatch: class Everything(str):
def __ne__(self, __value: object) -> bool:
return False
class LoopBasicBatch:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return { return {
"required": { "required": {
"nb_loops": ("INT", {"default": 1, "min": 0, "max": 1000, "step": 1}), "nb_loops": ("INT", {"default": 1, "min": 0, "max": 1000, "step": 1}),
"default_text": ("STRING", {"default": "Default input"})
},
"optional": {
"input": (Everything("*"),)
}, },
} }
RETURN_TYPES = ("INT",) RETURN_TYPES = (Everything("*"),)
OUTPUT_IS_LIST = (True, False) RETURN_NAMES = ("output",)
OUTPUT_IS_LIST = (True,)
FUNCTION = "create_loop_basic_batch" FUNCTION = "create_loop_basic_batch"
CATEGORY = "Bjornulf" CATEGORY = "Bjornulf"
def create_loop_basic_batch(self, nb_loops): def create_loop_basic_batch(self, nb_loops, default_text, input=None):
range_values = list() if input is not None:
while nb_loops > 0: return ([input] * nb_loops,)
range_values.append(1)
nb_loops -= 1 # Determine the type of the default_text
return (range_values,) if default_text.isdigit():
self.RETURN_TYPES = ("INT",)
output = int(default_text)
elif default_text.replace('.', '', 1).isdigit() and default_text.count('.') == 1:
self.RETURN_TYPES = ("FLOAT",)
output = float(default_text)
else:
self.RETURN_TYPES = ("STRING",)
output = default_text
return ([output] * nb_loops,)

View File

@@ -1,7 +1,7 @@
[project] [project]
name = "bjornulf_custom_nodes" name = "bjornulf_custom_nodes"
description = "Nodes: Ollama, Text to Speech, Combine Texts, Random Texts, Save image for Bjornulf LobeChat, Text with random Seed, Random line from input, Combine images, Image to grayscale (black & white), Remove image Transparency (alpha), Resize Image, ..." description = "Nodes: Ollama, Text to Speech, Combine Texts, Random Texts, Save image for Bjornulf LobeChat, Text with random Seed, Random line from input, Combine images, Image to grayscale (black & white), Remove image Transparency (alpha), Resize Image, ..."
version = "0.29" version = "0.31"
license = {file = "LICENSE"} license = {file = "LICENSE"}
[project.urls] [project.urls]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 73 KiB

BIN
screenshots/loop3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

BIN
screenshots/loop4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

BIN
screenshots/show.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

BIN
screenshots/write.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -1,18 +0,0 @@
class ShowFloat:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"float_value": ("FLOAT", {"forceInput": True}),
},
}
INPUT_IS_LIST = True
RETURN_TYPES = ()
FUNCTION = "show_float"
OUTPUT_NODE = True
INPUT_IS_LIST = (True,)
CATEGORY = "Bjornulf"
def show_float(self, float_value):
return {"ui": {"text": float_value}}

View File

@@ -1,18 +0,0 @@
class ShowInt:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"int_value": ("INT", {"forceInput": True}),
},
}
INPUT_IS_LIST = True
RETURN_TYPES = ()
FUNCTION = "show_int"
OUTPUT_NODE = True
INPUT_IS_LIST = (True,)
CATEGORY = "Bjornulf"
def show_int(self, int_value):
return {"ui": {"text": int_value}}

View File

@@ -1,18 +1,43 @@
class Everything(str):
def __ne__(self, _):
return False
class ShowText: class ShowText:
@classmethod @classmethod
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
return { return {
"required": { "required": {
"text_value": ("STRING", {"forceInput": True}), "text_int_float": (Everything("*"), {"forceInput": True}),
}, },
} }
INPUT_IS_LIST = True
RETURN_TYPES = () RETURN_TYPES = ()
FUNCTION = "show_text" FUNCTION = "show_text"
OUTPUT_NODE = True OUTPUT_NODE = True
INPUT_IS_LIST = (True,) INPUT_IS_LIST = (True,)
CATEGORY = "Bjornulf" CATEGORY = "Bjornulf"
def show_text(self, text_value): def detect_type(self, value):
return {"ui": {"text": text_value}} if isinstance(value, int):
return 'integer'
elif isinstance(value, float):
# Check if it has a decimal part
if value % 1 == 0:
return 'float' if str(value).endswith('.0') else 'integer'
return 'float'
elif isinstance(value, str):
try:
float_val = float(value)
if '.' in value:
return 'float string'
if float_val.is_integer():
return 'integer string'
return 'float string'
except ValueError:
return 'normal string'
else:
return 'other type'
def show_text(self, text_int_float):
type_info = [f"{value}" for value in text_int_float]
return {"ui": {"text": type_info}}

View File

@@ -1,14 +0,0 @@
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "Bjornulf.RandomLineFromInput",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_RandomLineFromInput") {
// Set seed widget to hidden input
const seedWidget = node.widgets.find(w => w.name === "seed");
if (seedWidget) {
seedWidget.type = "HIDDEN";
}
}
}
});

View File

@@ -1,19 +0,0 @@
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "Bjornulf.TextToStringAndSeed",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_TextToStringAndSeed") {
// Set seed widget to hidden input
const seedWidget = node.widgets.find(w => w.name === "seed");
if (seedWidget) {
seedWidget.type = "HIDDEN";
}
// Set seed widget to hidden input
const controlWidget = node.widgets.find(w => w.name === "control_after_generate");
if (controlWidget) {
controlWidget.type = "HIDDEN";
}
}
}
});

View File

@@ -36,10 +36,10 @@ app.registerExtension({
}; };
// Set seed widget to hidden input // Set seed widget to hidden input
const seedWidget = node.widgets.find(w => w.name === "seed"); // const seedWidget = node.widgets.find(w => w.name === "seed");
if (seedWidget) { // if (seedWidget) {
seedWidget.type = "HIDDEN"; // seedWidget.type = "HIDDEN";
} // }
// Move number_of_inputs to the top initially // Move number_of_inputs to the top initially
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs"); const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");

View File

@@ -1,76 +0,0 @@
import { app } from "../../../scripts/app.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
// Styles for the text area
const textAreaStyles = {
readOnly: true,
opacity: 1,
padding: '10px',
border: '1px solid #ccc',
borderRadius: '5px',
backgroundColor: '#222',
color: 'Lime',
fontFamily: 'Arial, sans-serif',
fontSize: '14px',
lineHeight: '1.4',
resize: 'vertical',
overflowY: 'auto',
};
// Displays input text on a node
app.registerExtension({
name: "Bjornulf.ShowFloat",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "Bjornulf_ShowFloat") {
function createStyledTextArea(text) {
const widget = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
widget.inputEl.readOnly = true;
const textArea = widget.inputEl;
Object.assign(textArea.style, textAreaStyles);
textArea.classList.add('bjornulf-show-text');
widget.value = text;
return widget;
}
function populate(text) {
if (this.widgets) {
for (let i = 1; i < this.widgets.length; i++) {
this.widgets[i].onRemove?.();
}
this.widgets.length = 1;
}
const v = Array.isArray(text) ? text : [text];
for (const list of v) {
if (list) {
createStyledTextArea.call(this, 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);
}
};
}
},
});

View File

@@ -1,76 +0,0 @@
import { app } from "../../../scripts/app.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
// Styles for the text area
const textAreaStyles = {
readOnly: true,
opacity: 1,
padding: '10px',
border: '1px solid #ccc',
borderRadius: '5px',
backgroundColor: '#222',
color: 'Lime',
fontFamily: 'Arial, sans-serif',
fontSize: '14px',
lineHeight: '1.4',
resize: 'vertical',
overflowY: 'auto',
};
// Displays input text on a node
app.registerExtension({
name: "Bjornulf.ShowInt",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "Bjornulf_ShowInt") {
function createStyledTextArea(text) {
const widget = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
widget.inputEl.readOnly = true;
const textArea = widget.inputEl;
Object.assign(textArea.style, textAreaStyles);
textArea.classList.add('bjornulf-show-text');
widget.value = text;
return widget;
}
function populate(text) {
if (this.widgets) {
for (let i = 1; i < this.widgets.length; i++) {
this.widgets[i].onRemove?.();
}
this.widgets.length = 1;
}
const v = Array.isArray(text) ? text : [text];
for (const list of v) {
if (list) {
createStyledTextArea.call(this, 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);
}
};
}
},
});

View File

@@ -5,7 +5,8 @@ import { ComfyWidgets } from "../../../scripts/widgets.js";
const textAreaStyles = { const textAreaStyles = {
readOnly: true, readOnly: true,
opacity: 1, opacity: 1,
padding: '10px', padding: '4px',
paddingLeft: '7px',
border: '1px solid #ccc', border: '1px solid #ccc',
borderRadius: '5px', borderRadius: '5px',
backgroundColor: '#222', backgroundColor: '#222',
@@ -13,7 +14,7 @@ const textAreaStyles = {
fontFamily: 'Arial, sans-serif', fontFamily: 'Arial, sans-serif',
fontSize: '14px', fontSize: '14px',
lineHeight: '1.4', lineHeight: '1.4',
resize: 'vertical', resize: 'none',
overflowY: 'auto', overflowY: 'auto',
}; };
@@ -22,32 +23,36 @@ app.registerExtension({
name: "Bjornulf.ShowText", name: "Bjornulf.ShowText",
async beforeRegisterNodeDef(nodeType, nodeData, app) { async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "Bjornulf_ShowText") { if (nodeData.name === "Bjornulf_ShowText") {
function createStyledTextArea(text) {
const widget = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
widget.inputEl.readOnly = true;
const textArea = widget.inputEl;
Object.assign(textArea.style, textAreaStyles);
textArea.classList.add('bjornulf-show-text');
widget.value = text;
return widget;
}
function populate(text) { function populate(text) {
if (this.widgets) { if (this.widgets) {
for (let i = 1; i < this.widgets.length; i++) { const pos = this.widgets.findIndex((w) => w.name === "text");
this.widgets[i].onRemove?.(); if (pos !== -1) {
} for (let i = pos; i < this.widgets.length; i++) {
this.widgets.length = 1; this.widgets[i].onRemove?.();
} }
this.widgets.length = pos;
const v = Array.isArray(text) ? text : [text];
for (const list of v) {
if (list) {
createStyledTextArea.call(this, list);
} }
} }
for (const list of text) {
const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
w.inputEl.readOnly = true;
Object.assign(w.inputEl.style, textAreaStyles);
// Improved type detection
let color = 'Lime'; // Default color for strings
const value = list.toString().trim();
if (/^-?\d+$/.test(value)) {
color = '#0096FF'; // Integer
} else if (/^-?\d*\.?\d+$/.test(value)) {
color = 'orange'; // Float
}
w.inputEl.style.color = color;
w.value = list;
}
requestAnimationFrame(() => { requestAnimationFrame(() => {
const sz = this.computeSize(); const sz = this.computeSize();
if (sz[0] < this.size[0]) sz[0] = this.size[0]; if (sz[0] < this.size[0]) sz[0] = this.size[0];

View File

@@ -1,17 +1,9 @@
import re
import random
import time
class WriteText: class WriteText:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return { return {
"required": { "required": {
"text": ("STRING", {"multiline": True}), "text": ("STRING", {"multiline": True, "lines": 10}),
},
"hidden": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"control_after_update": ("INT", {"default": 0})
}, },
} }
@@ -21,24 +13,5 @@ class WriteText:
OUTPUT_NODE = True OUTPUT_NODE = True
CATEGORY = "Bjornulf" CATEGORY = "Bjornulf"
def write_text(self, text, seed=None, control_after_update=None): def write_text(self, text):
# If seed is not provided, generate a new one return (text,)
if seed is None:
seed = int(time.time() * 1000)
# Use the seed to initialize the random number generator
random.seed(seed)
def replace_random(match):
options = match.group(1).split('|')
return random.choice(options)
pattern = r'\{([^}]+)\}'
result = re.sub(pattern, replace_random, text)
return (result,)
@classmethod
def IS_CHANGED(s, text, seed=None, control_after_update=None):
# This method is called to determine if the node needs to be re-executed
return float("nan") # Always re-execute to ensure consistency

48
write_text_advanced.py Normal file
View File

@@ -0,0 +1,48 @@
import re
import random
import time
import logging
class WriteTextAdvanced:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"multiline": True, "lines": 10}),
},
"optional": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "write_text_special"
OUTPUT_NODE = True
CATEGORY = "Bjornulf"
def write_text_special(self, text, seed=None):
logging.info(f"Raw text: {text}")
# If seed is not provided, generate a new one
if len(text) > 10000:
return ("Text too large to process at once. Please split into smaller parts.",)
if seed is None or seed == 0:
seed = int(time.time() * 1000)
random.seed(seed)
pattern = r'\{([^}]+)\}'
def replace_random(match):
return random.choice(match.group(1).split('|'))
result = re.sub(pattern, replace_random, text)
logging.info(f"Picked text: {result}")
return (result,)
@classmethod
def IS_CHANGED(s, text, seed=None):
return (text, seed)

View File

@@ -1,43 +0,0 @@
import re
import random
import time
import logging
class WriteTextInConsole:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"multiline": True}),
}
}
# INPUT_IS_LIST = True
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "write_text_in_console"
OUTPUT_NODE = True
OUTPUT_IS_LIST = (False,)
CATEGORY = "Bjornulf"
def write_text_in_console(self, text, seed=None, control_after_update=None):
logging.info(f"Text: {text}")
# If seed is not provided, generate a new one
if seed is None:
seed = int(time.time() * 1000)
# Use the seed to initialize the random number generator
random.seed(seed)
def replace_random(match):
options = match.group(1).split('|')
return random.choice(options)
pattern = r'\{([^}]+)\}'
result = re.sub(pattern, replace_random, text)
return (result,)
@classmethod
def IS_CHANGED(s, text, seed=None, control_after_update=None):
# This method is called to determine if the node needs to be re-executed
return float("nan") # Always re-execute to ensure consistency