Add Dumas character helper node

This commit is contained in:
2026-08-14 13:07:58 +00:00
parent f261a048b7
commit 49cb16338d
3 changed files with 257 additions and 0 deletions
+191
View File
@@ -255,6 +255,90 @@ def _tensor_image_to_pil_image(tensor):
return Image.fromarray(np.clip(image_array, 0, 255).astype(np.uint8))
def _normalize_free_text(value):
return " ".join(str(value or "").split()).strip()
def _label_for_character(name, character_id):
return _normalize_free_text(name) or _normalize_free_text(character_id) or "the character"
def _format_height_text(feet, inches):
feet_value = str(feet or "").strip()
inches_value = str(inches or "").strip()
if not feet_value and not inches_value:
return ""
if not feet_value:
return f'{int(inches_value)}"'
if not inches_value:
return f"{int(feet_value)}'"
return f'{int(feet_value)}\'{int(inches_value)}"'
def _ensure_sentence(value):
text = _normalize_free_text(value)
if not text:
return ""
if text[-1] not in ".!?":
text += "."
return text
def _build_character_helper_text(
primary_picture_id,
secondary_picture_id,
character_id,
name,
height_feet,
height_inches,
accent,
general,
):
primary_picture = int(primary_picture_id)
secondary_picture = int(secondary_picture_id)
character_name = _normalize_free_text(name)
character_id = _normalize_free_text(character_id)
accent = _normalize_free_text(accent)
general = _ensure_sentence(general)
character_label = _label_for_character(character_name, character_id)
if character_name:
first_line = (
f"<Picture {primary_picture}> and <Picture {secondary_picture}> reference "
f"the same character who is called {character_name}."
)
elif character_id:
first_line = (
f"<Picture {primary_picture}> and <Picture {secondary_picture}> reference "
f'the same character with ID "{character_id}".'
)
else:
first_line = (
f"<Picture {primary_picture}> and <Picture {secondary_picture}> reference "
"the same character."
)
lines = [
first_line,
f"<Picture {primary_picture}> is the primary full-body reference for {character_label}.",
]
if character_id:
lines.append(f'The character ID string is "{character_id}".')
height_text = _format_height_text(height_feet, height_inches)
if height_text:
lines.append(f"{character_label} is {height_text} tall.")
if accent:
lines.append(f"{character_label} has a {accent} accent.")
if general:
lines.append(general)
return "\n".join(lines)
class DumasImageCompareNode:
DESCRIPTION = (
"Dumas Image Compare shows the difference between two images directly on "
@@ -661,11 +745,117 @@ class DumasH3PlanExtractSceneImagesNode:
return (passthrough_plan, *images, _connected_image_count(images))
class DumasCharacterHelperNode:
DESCRIPTION = (
"Build a reusable character reference string from two IMAGE sockets and "
"simple identity fields, while passing both images through unchanged."
)
RETURN_TYPES = ("IMAGE", "IMAGE", "STRING")
RETURN_NAMES = ("image1", "image2", "character_text")
FUNCTION = "build_character_text"
CATEGORY = "Dumas/String"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image1": ("IMAGE", {"tooltip": "Primary image to pass through and describe."}),
"image2": ("IMAGE", {"tooltip": "Secondary image to pass through and describe."}),
"image1_picture_id": (
["2", "3", "4", "5", "6", "7"],
{
"default": "2",
"tooltip": "Picture number to mention for image1 in the output string.",
},
),
"image2_picture_id": (
["2", "3", "4", "5", "6", "7"],
{
"default": "3",
"tooltip": "Picture number to mention for image2 in the output string.",
},
),
"character_id": (
"STRING",
{
"default": "",
"multiline": False,
"tooltip": "Optional character ID string to include in the output text.",
},
),
"name": (
"STRING",
{
"default": "",
"multiline": False,
"tooltip": "Character name used in the main reference sentences.",
},
),
"height_feet": (
["", "3", "4", "5", "6", "7", "8"],
{
"default": "",
"tooltip": "Optional feet component for the character's height.",
},
),
"height_inches": (
["", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"],
{
"default": "",
"tooltip": "Optional inches component for the character's height.",
},
),
"accent": (
"STRING",
{
"default": "",
"multiline": False,
"tooltip": "Optional short accent description.",
},
),
"general": (
"STRING",
{
"default": "",
"multiline": True,
"tooltip": "Optional freeform details appended as the last sentence.",
},
),
}
}
def build_character_text(
self,
image1,
image2,
image1_picture_id,
image2_picture_id,
character_id,
name,
height_feet,
height_inches,
accent,
general,
):
text = _build_character_helper_text(
image1_picture_id,
image2_picture_id,
character_id,
name,
height_feet,
height_inches,
accent,
general,
)
return (image1, image2, text)
NODE_CLASS_MAPPINGS = {
"DumasImageCompare": DumasImageCompareNode,
"DumasSaveImage": DumasSaveImageNode,
"DumasH3PlanAttachSceneImages": DumasH3PlanAttachSceneImagesNode,
"DumasH3PlanExtractSceneImages": DumasH3PlanExtractSceneImagesNode,
"DumasCharacterHelper": DumasCharacterHelperNode,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -673,4 +863,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"DumasSaveImage": "Save Image Dumas",
"DumasH3PlanAttachSceneImages": "Dumas H3 Plan Attach Scene Images",
"DumasH3PlanExtractSceneImages": "Dumas H3 Plan Extract Scene Images",
"DumasCharacterHelper": "Dumas Character Helper",
}