From a51141cb26b9d1e68a19d0224be7752940b8fed0 Mon Sep 17 00:00:00 2001 From: Chris Dumas Date: Mon, 7 Sep 2026 07:46:24 +0000 Subject: [PATCH] Add location helper reference outputs --- README.md | 7 ++--- dumas_image_nodes.py | 25 +++++++++++++++--- tests/test_dumas_image_nodes.py | 46 ++++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 948652f..4312ef5 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,9 @@ - `Dumas Location Helper` - Inputs: `image1`, `image2`, picture IDs, `location_id`, `name`, `alias`, `description`, `general` - - Outputs: `image1`, `image2`, `reference_prompt` - - Matching general-purpose helper for environments/locations: pass two images through unchanged and emit location reference prompt text. + - Outputs: `image1`, `image2`, `reference_prompt`, `reference1`, `reference2` + - Matching general-purpose helper for environments/locations: pass two images through unchanged, emit location reference prompt text, and provide two structured `REFERENCE` objects for the prompt curator. + - The structured references carry the same location name, alias, description, and notes, so mentioning the location name in `Dumas H3 Prompt Curator` can include both helper images and the location context automatically. - `Dumas Anchor Style` - Inputs: `anchor_style`, `style_description` @@ -262,7 +263,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. 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, 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, assigns the final `` numbering, and outputs only the compacted images the prompt actually mentions. `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 8ca1069..9c1f233 100644 --- a/dumas_image_nodes.py +++ b/dumas_image_nodes.py @@ -2164,8 +2164,8 @@ class DumasLocationHelperNode: "Build a general location reference prompt from two IMAGE sockets plus " "simple environment fields, while passing both images through unchanged." ) - RETURN_TYPES = ("IMAGE", "IMAGE", "STRING") - RETURN_NAMES = ("image1", "image2", "reference_prompt") + RETURN_TYPES = ("IMAGE", "IMAGE", "STRING", _REFERENCE_TYPE, _REFERENCE_TYPE) + RETURN_NAMES = ("image1", "image2", "reference_prompt", "reference1", "reference2") FUNCTION = "build_location_text" CATEGORY = "Dumas/MiniMax" @@ -2253,7 +2253,26 @@ class DumasLocationHelperNode: description, general, ) - return (image1, image2, text) + common = { + "kind": "location", + "explicit_id": location_id, + "name": name, + "aliases": alias, + "description": description, + "general": general, + "facts": {}, + } + reference1 = make_reference( + image=image1, + summary="Primary location reference.", + **common, + ) + reference2 = make_reference( + image=image2, + summary="Secondary location reference.", + **common, + ) + return (image1, image2, text, reference1, reference2) class DumasCharacterReferenceNode: diff --git a/tests/test_dumas_image_nodes.py b/tests/test_dumas_image_nodes.py index 511d9a0..e862f83 100644 --- a/tests/test_dumas_image_nodes.py +++ b/tests/test_dumas_image_nodes.py @@ -421,7 +421,17 @@ class DumasImageNodeTests(unittest.TestCase): self.assertIn("Coffee Shop is also known as Cafe Interior", result[2]) self.assertIn("Warm tungsten lighting, narrow counter, rainy front window.", result[2]) self.assertIn("Evening ambience, cramped but cozy.", result[2]) - self.assertEqual(len(result), 3) + self.assertIs(result[3]["image"], image1) + self.assertIs(result[4]["image"], image2) + self.assertEqual(result[3]["kind"], "location") + self.assertEqual(result[4]["kind"], "location") + self.assertEqual(result[3]["id"], "coffee-shop-01") + self.assertEqual(result[4]["id"], "coffee-shop-01") + self.assertEqual(result[3]["name"], "Coffee Shop") + self.assertEqual(result[3]["aliases"], ["Cafe Interior"]) + self.assertEqual(result[3]["description"], "Warm tungsten lighting, narrow counter, rainy front window") + self.assertEqual(result[3]["general"], "Evening ambience, cramped but cozy") + self.assertEqual(len(result), 5) def test_soundscape_helper_defaults_to_selected_preset_description(self): node = self.image_nodes.DumasSoundscapeHelperNode() @@ -560,6 +570,40 @@ class DumasImageNodeTests(unittest.TestCase): self.assertIn("exactly one named character: Dave", result[0]) self.assertNotIn("exactly 2 named characters", result[0]) + def test_h3_prompt_curator_uses_location_helper_references_by_name(self): + helper = self.image_nodes.DumasLocationHelperNode() + curator = self.image_nodes.DumasH3PromptCuratorNode() + image1 = FakeTensorBatch() + image2 = FakeTensorBatch() + helper_result = helper.build_location_text( + image1=image1, + image2=image2, + image1_picture_id="1", + image2_picture_id="2", + location_id="coffee_shop", + name="Coffee Shop", + alias="Cafe Interior", + description="Warm tungsten lighting, narrow counter, rainy front window", + general="Evening ambience, cramped but cozy", + ) + + result = curator.curate_prompt( + action_prompt="A slow push through the Coffee Shop as rain streaks the windows.", + anatomy_guard="on", + subject_count_guard="auto", + ref_1=helper_result[3], + ref_2=helper_result[4], + ) + + self.assertIs(result[1], image1) + self.assertIs(result[2], image2) + self.assertEqual(result[10], 2) + self.assertIn(" Coffee Shop", result[0]) + self.assertIn(" Coffee Shop", result[0]) + self.assertIn("Location context for Coffee Shop", result[0]) + self.assertIn("Warm tungsten lighting", result[0]) + self.assertNotIn("Subject count guard:", result[0]) + def test_h3_prompt_curator_defaults_anatomy_guard_to_on(self): required = self.image_nodes.DumasH3PromptCuratorNode.INPUT_TYPES()["required"]