Update H3 character helper outputs

This commit is contained in:
2026-08-25 22:51:46 +00:00
parent 4f27c16505
commit 9d70f74b23
2 changed files with 97 additions and 14 deletions
+56 -14
View File
@@ -552,6 +552,30 @@ def _build_character_helper_text(
return "\n".join(lines) 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: class DumasImageCompareNode:
DESCRIPTION = ( DESCRIPTION = (
"Dumas Image Compare shows the difference between two images directly on " "Dumas Image Compare shows the difference between two images directly on "
@@ -1092,13 +1116,14 @@ class DumasH3PlanExtractSceneImagesNode:
class DumasCharacterHelperNode: class DumasCharacterHelperNode:
DESCRIPTION = ( DESCRIPTION = (
"Build a reusable character reference string from two IMAGE sockets and " "Build a MiniMax H3-ready character reference prompt and wardrobe sheet "
"simple identity fields, while passing both images through unchanged." "from two IMAGE sockets plus simple identity fields, while passing both "
"images through unchanged."
) )
RETURN_TYPES = ("IMAGE", "IMAGE", "STRING") RETURN_TYPES = ("IMAGE", "IMAGE", "STRING", "STRING")
RETURN_NAMES = ("image1", "image2", "character_text") RETURN_NAMES = ("image1", "image2", "reference_prompt", "wardrobe")
FUNCTION = "build_character_text" FUNCTION = "build_character_text"
CATEGORY = "Dumas/String" CATEGORY = "Dumas/MiniMax"
@classmethod @classmethod
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
@@ -1107,17 +1132,17 @@ class DumasCharacterHelperNode:
"image1": ("IMAGE", {"tooltip": "Primary image to pass through and describe."}), "image1": ("IMAGE", {"tooltip": "Primary image to pass through and describe."}),
"image2": ("IMAGE", {"tooltip": "Secondary image to pass through and describe."}), "image2": ("IMAGE", {"tooltip": "Secondary image to pass through and describe."}),
"image1_picture_id": ( "image1_picture_id": (
["2", "3", "4", "5", "6", "7"], ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
{ {
"default": "2", "default": "1",
"tooltip": "Picture number to mention for image1 in the output string.", "tooltip": "Picture number to mention for image1 in the H3 reference prompt.",
}, },
), ),
"image2_picture_id": ( "image2_picture_id": (
["2", "3", "4", "5", "6", "7"], ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
{ {
"default": "3", "default": "2",
"tooltip": "Picture number to mention for image2 in the output string.", "tooltip": "Picture number to mention for image2 in the H3 reference prompt.",
}, },
), ),
"character_id": ( "character_id": (
@@ -1203,7 +1228,15 @@ class DumasCharacterHelperNode:
{ {
"default": "", "default": "",
"multiline": True, "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, height_inches,
accent, accent,
general, general,
wardrobe,
): ):
text = _build_character_helper_text( text = _build_character_helper_text(
image1_picture_id, image1_picture_id,
@@ -1242,7 +1276,13 @@ class DumasCharacterHelperNode:
accent, accent,
general, 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 = { NODE_CLASS_MAPPINGS = {
@@ -1252,6 +1292,7 @@ NODE_CLASS_MAPPINGS = {
"DumasH3PlanAttachSceneImages": DumasH3PlanAttachSceneImagesNode, "DumasH3PlanAttachSceneImages": DumasH3PlanAttachSceneImagesNode,
"DumasH3PlanExtractSceneImages": DumasH3PlanExtractSceneImagesNode, "DumasH3PlanExtractSceneImages": DumasH3PlanExtractSceneImagesNode,
"DumasCharacterHelper": DumasCharacterHelperNode, "DumasCharacterHelper": DumasCharacterHelperNode,
"DumasH3CharacterHelper": DumasCharacterHelperNode,
} }
NODE_DISPLAY_NAME_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = {
@@ -1260,5 +1301,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"DumasLoadImagesFolder": "Load Images from Folder Dumas", "DumasLoadImagesFolder": "Load Images from Folder Dumas",
"DumasH3PlanAttachSceneImages": "Dumas H3 Plan Attach Scene Images", "DumasH3PlanAttachSceneImages": "Dumas H3 Plan Attach Scene Images",
"DumasH3PlanExtractSceneImages": "Dumas H3 Plan Extract Scene Images", "DumasH3PlanExtractSceneImages": "Dumas H3 Plan Extract Scene Images",
"DumasCharacterHelper": "Dumas Character Helper", "DumasCharacterHelper": "Dumas H3 Character Helper",
"DumasH3CharacterHelper": "Dumas H3 Character Helper",
} }
+41
View File
@@ -260,6 +260,7 @@ class DumasImageNodeTests(unittest.TestCase):
height_inches="2", height_inches="2",
accent="English", accent="English",
general="wears a long grey coat", general="wears a long grey coat",
wardrobe="weathered red flight jacket, grey cargo shorts, black boots",
) )
self.assertIs(result[0], image1) self.assertIs(result[0], image1)
@@ -274,6 +275,10 @@ class DumasImageNodeTests(unittest.TestCase):
"wears a long grey coat." "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): def test_character_helper_handles_missing_optional_fields(self):
node = self.image_nodes.DumasCharacterHelperNode() node = self.image_nodes.DumasCharacterHelperNode()
@@ -296,6 +301,7 @@ class DumasImageNodeTests(unittest.TestCase):
height_inches="", height_inches="",
accent="", accent="",
general="", general="",
wardrobe="",
) )
self.assertEqual( self.assertEqual(
@@ -306,6 +312,41 @@ class DumasImageNodeTests(unittest.TestCase):
"<Picture 6> is a frontal facial reference for the character." "<Picture 6> 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],
(
"<Picture 1> and <Picture 9> reference the same character who is called Kristy.\n"
"<Picture 1> is the primary full-body reference for Kristy.\n"
"<Picture 9> 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): def test_save_image_returns_ui_entries_for_output_folder(self):
node = self.image_nodes.DumasSaveImageNode() node = self.image_nodes.DumasSaveImageNode()