diff --git a/dumas_image_nodes.py b/dumas_image_nodes.py index a1d138a..731a2ce 100644 --- a/dumas_image_nodes.py +++ b/dumas_image_nodes.py @@ -552,6 +552,30 @@ def _build_character_helper_text( return "\n".join(lines) +def _build_character_wardrobe_text(wardrobe, character_id, name, alias): + text = _normalize_free_text(wardrobe) + if not text: + return "" + + if text.lower().startswith("wardrobe:"): + text = text.split(":", 1)[1].strip() + if not text: + return "" + + # If the user already authored a full H3-style sheet entry, leave it alone. + if "=" in text or ":" in text: + return text + + character_label = ( + _normalize_free_text(name) + or _normalize_free_text(alias) + or _normalize_free_text(character_id) + ) + if character_label: + return f"{character_label} = {text}" + return text + + class DumasImageCompareNode: DESCRIPTION = ( "Dumas Image Compare shows the difference between two images directly on " @@ -1092,13 +1116,14 @@ class DumasH3PlanExtractSceneImagesNode: class DumasCharacterHelperNode: DESCRIPTION = ( - "Build a reusable character reference string from two IMAGE sockets and " - "simple identity fields, while passing both images through unchanged." + "Build a MiniMax H3-ready character reference prompt and wardrobe sheet " + "from two IMAGE sockets plus simple identity fields, while passing both " + "images through unchanged." ) - RETURN_TYPES = ("IMAGE", "IMAGE", "STRING") - RETURN_NAMES = ("image1", "image2", "character_text") + RETURN_TYPES = ("IMAGE", "IMAGE", "STRING", "STRING") + RETURN_NAMES = ("image1", "image2", "reference_prompt", "wardrobe") FUNCTION = "build_character_text" - CATEGORY = "Dumas/String" + CATEGORY = "Dumas/MiniMax" @classmethod def INPUT_TYPES(cls): @@ -1107,17 +1132,17 @@ class DumasCharacterHelperNode: "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"], + ["1", "2", "3", "4", "5", "6", "7", "8", "9"], { - "default": "2", - "tooltip": "Picture number to mention for image1 in the output string.", + "default": "1", + "tooltip": "Picture number to mention for image1 in the H3 reference prompt.", }, ), "image2_picture_id": ( - ["2", "3", "4", "5", "6", "7"], + ["1", "2", "3", "4", "5", "6", "7", "8", "9"], { - "default": "3", - "tooltip": "Picture number to mention for image2 in the output string.", + "default": "2", + "tooltip": "Picture number to mention for image2 in the H3 reference prompt.", }, ), "character_id": ( @@ -1203,7 +1228,15 @@ class DumasCharacterHelperNode: { "default": "", "multiline": True, - "tooltip": "Optional freeform details appended as the last sentence.", + "tooltip": "Optional non-clothing details appended as the last sentence of the H3 reference prompt.", + }, + ), + "wardrobe": ( + "STRING", + { + "default": "", + "multiline": True, + "tooltip": "Optional H3 wardrobe/channel text. Plain clothing lists are auto-wrapped as 'Name = ...' when a name, alias, or character ID is present.", }, ), } @@ -1226,6 +1259,7 @@ class DumasCharacterHelperNode: height_inches, accent, general, + wardrobe, ): text = _build_character_helper_text( image1_picture_id, @@ -1242,7 +1276,13 @@ class DumasCharacterHelperNode: accent, general, ) - return (image1, image2, text) + wardrobe_text = _build_character_wardrobe_text( + wardrobe, + character_id, + name, + alias, + ) + return (image1, image2, text, wardrobe_text) NODE_CLASS_MAPPINGS = { @@ -1252,6 +1292,7 @@ NODE_CLASS_MAPPINGS = { "DumasH3PlanAttachSceneImages": DumasH3PlanAttachSceneImagesNode, "DumasH3PlanExtractSceneImages": DumasH3PlanExtractSceneImagesNode, "DumasCharacterHelper": DumasCharacterHelperNode, + "DumasH3CharacterHelper": DumasCharacterHelperNode, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -1260,5 +1301,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "DumasLoadImagesFolder": "Load Images from Folder Dumas", "DumasH3PlanAttachSceneImages": "Dumas H3 Plan Attach Scene Images", "DumasH3PlanExtractSceneImages": "Dumas H3 Plan Extract Scene Images", - "DumasCharacterHelper": "Dumas Character Helper", + "DumasCharacterHelper": "Dumas H3 Character Helper", + "DumasH3CharacterHelper": "Dumas H3 Character Helper", } diff --git a/tests/test_dumas_image_nodes.py b/tests/test_dumas_image_nodes.py index 98bf72a..1dd1735 100644 --- a/tests/test_dumas_image_nodes.py +++ b/tests/test_dumas_image_nodes.py @@ -260,6 +260,7 @@ class DumasImageNodeTests(unittest.TestCase): height_inches="2", accent="English", general="wears a long grey coat", + wardrobe="weathered red flight jacket, grey cargo shorts, black boots", ) self.assertIs(result[0], image1) @@ -274,6 +275,10 @@ class DumasImageNodeTests(unittest.TestCase): "wears a long grey coat." ), ) + self.assertEqual( + result[3], + "Dave = weathered red flight jacket, grey cargo shorts, black boots", + ) def test_character_helper_handles_missing_optional_fields(self): node = self.image_nodes.DumasCharacterHelperNode() @@ -296,6 +301,7 @@ class DumasImageNodeTests(unittest.TestCase): height_inches="", accent="", general="", + wardrobe="", ) self.assertEqual( @@ -306,6 +312,41 @@ class DumasImageNodeTests(unittest.TestCase): " is a frontal facial reference for the character." ), ) + self.assertEqual(result[3], "") + + def test_character_helper_allows_picture_one_and_preserves_full_sheet_wardrobe(self): + node = self.image_nodes.DumasCharacterHelperNode() + image1 = FakeTensorBatch() + image2 = FakeTensorBatch() + + result = node.build_character_text( + image1=image1, + image2=image2, + image1_picture_id="1", + image2_picture_id="9", + character_id="char_kristy", + name="Kristy", + alias="", + gender="", + age="", + nationality="", + occupation="", + height_feet="", + height_inches="", + accent="", + general="", + wardrobe="Kristy = black coat, silver boots", + ) + + self.assertEqual( + result[2], + ( + " and reference the same character who is called Kristy.\n" + " is the primary full-body reference for Kristy.\n" + " is a frontal facial reference for Kristy." + ), + ) + self.assertEqual(result[3], "Kristy = black coat, silver boots") def test_save_image_returns_ui_entries_for_output_folder(self): node = self.image_nodes.DumasSaveImageNode()