diff --git a/README.md b/README.md index 282366e..d68d2cc 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ - Outputs: `prompt`, `ref_image_1` through `ref_image_9`, `reference_count`, `debug`, `anchor`, `sounds`, `bgm`, `original_ref_1` through `original_ref_9`, `compiled_ref_description_1` through `compiled_ref_description_9` - Builds one standalone MiniMax H3 prompt from your final action text plus structured character/location references. - The action text can mention references by character/location name, alias, ``, or ``. Only mentioned references are emitted, and the output images are compacted/renumbered so skipped inputs do not leave gaps. - - Extra component outputs expose the cleaned anchor, sounds, BGM, and each selected original reference image plus its compiled reference description in compacted order. + - Extra component outputs expose the cleaned anchor, sounds, BGM, and each connected input reference image plus its compiled reference description in original socket order. These inspection/reuse outputs are not trimmed by the action text; only the final prompt and `ref_image_*` H3 reference outputs are trimmed. - Adds curated reference context, anatomy guard text, optional subject-count guard text, anchor/style text, `overall_soundscape:` text, and `background_music:` text while respecting MiniMax H3's reference-generation shape: one prompt plus up to nine reference images. - `Dumas H3 Shot Length` @@ -266,7 +266,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` and the local `ref2v` lane. The upstream H3 plan node cannot dynamically grow nine 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 nine IMAGE sockets per scene through chained helper nodes. -`Dumas Character Helper` is the restored two-image/text helper for general H3 workflows, and `Dumas Location Helper` mirrors it for scene/environment references. Both helpers also emit structured `REFERENCE` sockets for the curator. The structured `Dumas Character Reference` and `Dumas Location Reference` nodes remain available separately for workflows that want a single `REFERENCE` socket. `Dumas H3 Prompt Curator` consumes those structured references plus optional anchor, soundscape, and BGM strings, assigns the final `` numbering, and outputs only the compacted images the prompt actually mentions. +`Dumas Character Helper` is the restored two-image/text helper for general H3 workflows, and `Dumas Location Helper` mirrors it for scene/environment references. Both helpers also emit structured `REFERENCE` sockets for the curator. The structured `Dumas Character Reference` and `Dumas Location Reference` nodes remain available separately for workflows that want a single `REFERENCE` socket. `Dumas H3 Prompt Curator` consumes those structured references plus optional anchor, soundscape, and BGM strings, assigns the final `` numbering, and outputs compacted `ref_image_*` sockets for only the references the prompt actually mentions. Its `original_ref_*` and `compiled_ref_description_*` outputs mirror the connected input sockets for reuse/debugging even when a reference is not mentioned in the final prompt. `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 7e0d86a..9d7c8fa 100644 --- a/dumas_image_nodes.py +++ b/dumas_image_nodes.py @@ -1253,13 +1253,13 @@ def curate_h3_prompt( soundscape_text = _reference_text(soundscape) bgm_text = _reference_text(bgm) reference_description = "" - individual_reference_descriptions = [] + selected_reference_descriptions = [] if selected: - individual_reference_descriptions = [ + selected_reference_descriptions = [ _reference_context(ref, picture_number) for picture_number, (_slot, ref) in enumerate(selected, 1) ] - reference_description = " ".join(individual_reference_descriptions) + reference_description = " ".join(selected_reference_descriptions) prompt_parts = [] _append_prompt_section(prompt_parts, "Scene anchor", anchor_text) @@ -1279,9 +1279,16 @@ def curate_h3_prompt( prompt = prompt[: _H3_PROMPT_MAX_CHARS - 3].rstrip() + "..." images = [_reference_image(ref) for _slot, ref in selected] images.extend([None] * (_H3_PROMPT_REF_SLOTS - len(images))) - individual_reference_descriptions.extend( - [""] * (_H3_PROMPT_REF_SLOTS - len(individual_reference_descriptions)) - ) + original_images = [ + _reference_image(ref) if ref is not None else None + for ref in normalized_refs + ] + original_images.extend([None] * (_H3_PROMPT_REF_SLOTS - len(original_images))) + input_reference_descriptions = [ + _reference_context(ref, slot_number) if ref is not None else "" + for slot_number, ref in enumerate(normalized_refs, 1) + ] + input_reference_descriptions.extend([""] * (_H3_PROMPT_REF_SLOTS - len(input_reference_descriptions))) debug = ( f"Selected {len(selected)} reference(s): " + ", ".join( @@ -1299,8 +1306,8 @@ def curate_h3_prompt( anchor_text, soundscape_text, bgm_text, - *images[:_H3_PROMPT_REF_SLOTS], - *individual_reference_descriptions[:_H3_PROMPT_REF_SLOTS], + *original_images[:_H3_PROMPT_REF_SLOTS], + *input_reference_descriptions[:_H3_PROMPT_REF_SLOTS], ) diff --git a/tests/test_dumas_image_nodes.py b/tests/test_dumas_image_nodes.py index aea6fb3..d22e28f 100644 --- a/tests/test_dumas_image_nodes.py +++ b/tests/test_dumas_image_nodes.py @@ -503,13 +503,50 @@ class DumasImageNodeTests(unittest.TestCase): self.assertEqual(result[13], "steady rain") self.assertEqual(result[14], "low suspense music") self.assertIs(result[15], dave_image) - self.assertIs(result[16], cafe_image) - self.assertIsNone(result[17]) + self.assertIs(result[16], van_image) + self.assertIs(result[17], cafe_image) self.assertIn(" Dave", result[24]) self.assertNotIn(" Coffee Shop", result[24]) - self.assertIn(" Coffee Shop", result[25]) - self.assertIn("Location context for Coffee Shop", result[25]) - self.assertEqual(result[26], "") + self.assertIn(" Blue Van", result[25]) + self.assertIn("scuffed blue delivery van", result[25]) + self.assertIn(" Coffee Shop", result[26]) + self.assertIn("Location context for Coffee Shop", result[26]) + self.assertNotIn("Blue Van", prompt) + + def test_h3_prompt_curator_aux_reference_outputs_follow_input_sockets(self): + node = self.image_nodes.DumasH3PromptCuratorNode() + used_image = FakeTensorBatch() + unused_image = FakeTensorBatch() + used = self.image_nodes.make_reference( + kind="character", + image=used_image, + name="Maya", + description="short silver hair", + ) + unused = self.image_nodes.make_reference( + kind="location", + image=unused_image, + name="Unused Warehouse", + description="rusted loading bay doors", + ) + + result = node.curate_prompt( + action_prompt="Maya waits in the rain.", + anatomy_guard="off", + subject_count_guard="off", + ref_1=used, + ref_2=unused, + ) + + self.assertIs(result[1], used_image) + self.assertIsNone(result[2]) + self.assertEqual(result[10], 1) + self.assertNotIn("Unused Warehouse", result[0]) + self.assertIs(result[15], used_image) + self.assertIs(result[16], unused_image) + self.assertIn(" Maya", result[24]) + self.assertIn(" Unused Warehouse", result[25]) + self.assertIn("rusted loading bay doors", result[25]) def test_h3_prompt_curator_renumbers_explicit_reference_tags(self): node = self.image_nodes.DumasH3PromptCuratorNode()