diff --git a/README.md b/README.md index f9424ac..fd8bcd9 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,10 @@ - Reads back the seven optional images for a selected MiniMax H3 plan scene, for example by connecting the current `clip_index`. - `Dumas Character Helper` - - Inputs: `image1`, `image2`, `image1_picture_id`, `image2_picture_id`, `character_id`, `name`, `height_feet`, `height_inches`, `accent`, `general` + - Inputs: `image1`, `image2`, `image1_picture_id`, `image2_picture_id`, `character_id`, `name`, `alias`, `pronouns`, `age`, `nationality`, `occupation`, `height_feet`, `height_inches`, `accent`, `general` - Outputs: `image1`, `image2`, `character_text` - Passes both images through unchanged and builds a character reference string such as ` and reference the same character who is called Dave.` - - Appends optional sentences for character ID, height, accent, and freeform notes only when those fields are filled in. + - Appends optional sentences for non-visual facts such as ID, alias, pronouns, age, nationality, occupation, height, accent, and freeform notes only when those fields are valid and filled in. - `Dumas JSON String to Object` - Input: `json_string` @@ -189,7 +189,7 @@ decr -> use index - 1 `Dumas H3 Plan Attach Scene Images` and `Dumas H3 Plan Extract Scene Images` are a companion pair for `ComfyUI-MiniMaxH3-Contex-Loop`. The upstream H3 plan node cannot dynamically grow six new image sockets for every JSON-defined scene, so Dumas stores scene image bindings beside the plan using a lightweight token and an in-memory registry. That keeps `plan.json` archiving intact while still letting you wire up six IMAGE sockets per scene through chained helper nodes. -`Dumas Character Helper` lives in `Dumas/String`. Use the picture ID dropdowns to decide which `` tags get mentioned in the generated text, while the two IMAGE sockets continue downstream unchanged. +`Dumas Character Helper` lives in `Dumas/String`. Use the picture ID dropdowns to decide which `` tags get mentioned in the generated text, while the two IMAGE sockets continue downstream unchanged. The node is tuned for useful non-visible facts rather than visual descriptions already obvious from the reference images. `Dumas Strip Iteration Suffix` keeps the part before the first underscore and drops the rest. Names like `char123_pose_final.png` become `char123.png`, while names with no underscore such as `char123.png` are left untouched. diff --git a/dumas_image_nodes.py b/dumas_image_nodes.py index b6d3f9f..66859ab 100644 --- a/dumas_image_nodes.py +++ b/dumas_image_nodes.py @@ -290,11 +290,36 @@ def _ensure_sentence(value): return text +def _parse_positive_int(value): + text = str(value or "").strip() + if not text: + return None + try: + parsed = int(text) + except (TypeError, ValueError): + return None + if parsed <= 0: + return None + return parsed + + +def _indefinite_article(value): + text = _normalize_free_text(value).lower() + if not text: + return "a" + return "an" if text[0] in "aeiou" else "a" + + def _build_character_helper_text( primary_picture_id, secondary_picture_id, character_id, name, + alias, + pronouns, + age, + nationality, + occupation, height_feet, height_inches, accent, @@ -304,8 +329,13 @@ def _build_character_helper_text( secondary_picture = int(secondary_picture_id) character_name = _normalize_free_text(name) character_id = _normalize_free_text(character_id) + alias = _normalize_free_text(alias) + pronouns = _normalize_free_text(pronouns) + nationality = _normalize_free_text(nationality) + occupation = _normalize_free_text(occupation) accent = _normalize_free_text(accent) general = _ensure_sentence(general) + age_value = _parse_positive_int(age) character_label = _label_for_character(character_name, character_id) if character_name: @@ -333,12 +363,29 @@ def _build_character_helper_text( if character_id: lines.append(f'The character ID string is "{character_id}".') + if alias: + lines.append(f"{character_label} is also known as {alias}.") + + if pronouns: + lines.append(f"{character_label} uses {pronouns} pronouns.") + + if age_value is not None: + lines.append(f"{character_label} is {age_value} years old.") + + if nationality: + lines.append(f"{character_label} is {nationality}.") + + if occupation: + lines.append(f"{character_label} works as {occupation}.") + 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.") + lines.append( + f"{character_label} speaks in {_indefinite_article(accent)} {accent} accent." + ) if general: lines.append(general) @@ -798,6 +845,46 @@ class DumasCharacterHelperNode: "tooltip": "Character name used in the main reference sentences.", }, ), + "alias": ( + "STRING", + { + "default": "", + "multiline": False, + "tooltip": "Optional alternate name, codename, or nickname.", + }, + ), + "pronouns": ( + "STRING", + { + "default": "", + "multiline": False, + "tooltip": "Optional pronouns such as he/him or she/her.", + }, + ), + "age": ( + "STRING", + { + "default": "", + "multiline": False, + "tooltip": "Optional numeric age. Invalid values are omitted.", + }, + ), + "nationality": ( + "STRING", + { + "default": "", + "multiline": False, + "tooltip": "Optional nationality, origin, or cultural background.", + }, + ), + "occupation": ( + "STRING", + { + "default": "", + "multiline": False, + "tooltip": "Optional job, role, or function that is not visually obvious.", + }, + ), "height_feet": ( ["", "3", "4", "5", "6", "7", "8"], { @@ -839,6 +926,11 @@ class DumasCharacterHelperNode: image2_picture_id, character_id, name, + alias, + pronouns, + age, + nationality, + occupation, height_feet, height_inches, accent, @@ -849,6 +941,11 @@ class DumasCharacterHelperNode: image2_picture_id, character_id, name, + alias, + pronouns, + age, + nationality, + occupation, height_feet, height_inches, accent, diff --git a/tests/test_dumas_image_nodes.py b/tests/test_dumas_image_nodes.py index 93f4c29..c5763eb 100644 --- a/tests/test_dumas_image_nodes.py +++ b/tests/test_dumas_image_nodes.py @@ -251,9 +251,14 @@ class DumasImageNodeTests(unittest.TestCase): image2_picture_id="3", character_id="char_dave", name="Dave", + alias="The Locksmith", + pronouns="he/him", + age="41", + nationality="English", + occupation="a detective", height_feet="6", height_inches="2", - accent="northern English", + accent="English", general="wears a long grey coat", ) @@ -266,8 +271,13 @@ class DumasImageNodeTests(unittest.TestCase): " is the primary full-body reference for Dave.\n" " is a frontal facial reference for Dave.\n" 'The character ID string is "char_dave".\n' + "Dave is also known as The Locksmith.\n" + "Dave uses he/him pronouns.\n" + "Dave is 41 years old.\n" + "Dave is English.\n" + "Dave works as a detective.\n" "Dave is 6 feet 2 inches tall.\n" - "Dave has a northern English accent.\n" + "Dave speaks in an English accent.\n" "wears a long grey coat." ), ) @@ -284,6 +294,11 @@ class DumasImageNodeTests(unittest.TestCase): image2_picture_id="6", character_id="", name="", + alias="", + pronouns="", + age="unknown", + nationality="", + occupation="", height_feet="", height_inches="", accent="",