Expand H3 scene image helpers to 9 slots

This commit is contained in:
2026-08-25 17:42:10 +00:00
parent 34e9bea6d9
commit 30c7128810
3 changed files with 67 additions and 16 deletions
+6 -6
View File
@@ -23,15 +23,15 @@
- Supports manual picks, `Select all`, `First N`, and `Select random` so each run can choose one random image from the visible folder tree. - Supports manual picks, `Select all`, `First N`, and `Select random` so each run can choose one random image from the visible folder tree.
- `Dumas H3 Plan Attach Scene Images` - `Dumas H3 Plan Attach Scene Images`
- Inputs: `plan`, `scene_index`, optional `image1`..`image7` - Inputs: `plan`, `scene_index`, optional `image1`..`image9`
- Outputs: `plan`, `connected_images` - Outputs: `plan`, `connected_images`
- Attaches up to seven optional image sockets to one MiniMax H3 plan scene while keeping the plan JSON-serializable for the upstream archive system. - Attaches up to nine optional image sockets to one MiniMax H3 plan scene while keeping the plan JSON-serializable for the upstream archive system.
- Chain one node per scene that needs its own seven-image bundle. - Chain one node per scene that needs its own nine-image bundle.
- `Dumas H3 Plan Extract Scene Images` - `Dumas H3 Plan Extract Scene Images`
- Inputs: `plan`, `scene_index` - Inputs: `plan`, `scene_index`
- Outputs: `plan`, `image1`..`image7`, `connected_images` - Outputs: `plan`, `image1`..`image9`, `connected_images`
- Reads back the seven optional images for a selected MiniMax H3 plan scene, for example by connecting the current `clip_index`. - Reads back the nine optional images for a selected MiniMax H3 plan scene, for example by connecting the current `clip_index`.
- `Dumas Character Helper` - `Dumas Character Helper`
- Inputs: `image1`, `image2`, `image1_picture_id`, `image2_picture_id`, `character_id`, `name`, `alias`, `gender`, `age`, `nationality`, `occupation`, `height_feet`, `height_inches`, `accent`, `general` - Inputs: `image1`, `image2`, `image1_picture_id`, `image2_picture_id`, `character_id`, `name`, `alias`, `gender`, `age`, `nationality`, `occupation`, `height_feet`, `height_inches`, `accent`, `general`
@@ -193,7 +193,7 @@ decr -> use index - 1
`Save Image Dumas` leaves `folder` empty to use ComfyUI's output directory. `name` feeds `%input%` and `name_2` feeds `%input2%`, so a pattern like `project/%input%_%input2%_%counter%` can combine two upstream strings into the saved filename. `Save Image Dumas` leaves `folder` empty to use ComfyUI's output directory. `name` feeds `%input%` and `name_2` feeds `%input2%`, so a pattern like `project/%input%_%input2%_%counter%` can combine two upstream strings into the saved filename.
`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 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` lives in `Dumas/String`. Use the picture ID dropdowns to decide which `<Picture N>` 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 Character Helper` lives in `Dumas/String`. Use the picture ID dropdowns to decide which `<Picture N>` 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.
+27 -8
View File
@@ -23,6 +23,7 @@ _H3_PLAN_TYPE = "H3_CHAIN_PLAN"
_H3_PLAN_IMAGE_BINDINGS_KEY = "_dumas_scene_image_bindings" _H3_PLAN_IMAGE_BINDINGS_KEY = "_dumas_scene_image_bindings"
_H3_PLAN_IMAGE_BINDINGS = OrderedDict() _H3_PLAN_IMAGE_BINDINGS = OrderedDict()
_H3_PLAN_IMAGE_BINDINGS_CAP = 128 _H3_PLAN_IMAGE_BINDINGS_CAP = 128
_H3_PLAN_IMAGE_SLOTS = 9
_FOLDER_IMAGE_EXTS = (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif", ".tiff", ".tif") _FOLDER_IMAGE_EXTS = (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif", ".tiff", ".tif")
_LOAD_IMAGES_FOLDER_DEFAULT_STATE = { _LOAD_IMAGES_FOLDER_DEFAULT_STATE = {
"version": 1, "version": 1,
@@ -192,8 +193,10 @@ def _scene_images_tuple(
image5=None, image5=None,
image6=None, image6=None,
image7=None, image7=None,
image8=None,
image9=None,
): ):
return (image1, image2, image3, image4, image5, image6, image7) return (image1, image2, image3, image4, image5, image6, image7, image8, image9)
def _connected_image_count(images): def _connected_image_count(images):
@@ -907,7 +910,7 @@ class DumasLoadImagesFolderNode:
class DumasH3PlanAttachSceneImagesNode: class DumasH3PlanAttachSceneImagesNode:
DESCRIPTION = ( DESCRIPTION = (
"Attach up to seven optional IMAGE sockets to one H3 Chain Plan scene " "Attach up to nine optional IMAGE sockets to one H3 Chain Plan scene "
"without breaking the upstream plan archive format. Chain multiple " "without breaking the upstream plan archive format. Chain multiple "
"copies of this node to bind different scene indexes." "copies of this node to bind different scene indexes."
) )
@@ -919,7 +922,7 @@ class DumasH3PlanAttachSceneImagesNode:
@classmethod @classmethod
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
optional = {} optional = {}
for slot in range(1, 8): for slot in range(1, _H3_PLAN_IMAGE_SLOTS + 1):
optional[f"image{slot}"] = ( optional[f"image{slot}"] = (
"IMAGE", "IMAGE",
{ {
@@ -948,7 +951,7 @@ class DumasH3PlanAttachSceneImagesNode:
"step": 1, "step": 1,
"tooltip": ( "tooltip": (
"1-based scene index inside the H3 plan. Use one node per " "1-based scene index inside the H3 plan. Use one node per "
"scene that needs up to seven image sockets." "scene that needs up to nine image sockets."
), ),
}, },
), ),
@@ -967,10 +970,22 @@ class DumasH3PlanAttachSceneImagesNode:
image5=None, image5=None,
image6=None, image6=None,
image7=None, image7=None,
image8=None,
image9=None,
): ):
updated_plan = _clone_h3_plan(plan) updated_plan = _clone_h3_plan(plan)
scene_index = _normalize_h3_scene_index(updated_plan, scene_index) scene_index = _normalize_h3_scene_index(updated_plan, scene_index)
images = _scene_images_tuple(image1, image2, image3, image4, image5, image6, image7) images = _scene_images_tuple(
image1,
image2,
image3,
image4,
image5,
image6,
image7,
image8,
image9,
)
connected_count = _connected_image_count(images) connected_count = _connected_image_count(images)
token, scene_counts = _h3_plan_binding_entry(updated_plan) token, scene_counts = _h3_plan_binding_entry(updated_plan)
@@ -1001,7 +1016,7 @@ class DumasH3PlanAttachSceneImagesNode:
class DumasH3PlanExtractSceneImagesNode: class DumasH3PlanExtractSceneImagesNode:
DESCRIPTION = ( DESCRIPTION = (
"Read back the seven optional image bindings for one H3 Chain Plan scene. " "Read back the nine optional image bindings for one H3 Chain Plan scene. "
"Connect clip_index or another scene selector to recover the matching " "Connect clip_index or another scene selector to recover the matching "
"scene images downstream." "scene images downstream."
) )
@@ -1014,6 +1029,8 @@ class DumasH3PlanExtractSceneImagesNode:
"IMAGE", "IMAGE",
"IMAGE", "IMAGE",
"IMAGE", "IMAGE",
"IMAGE",
"IMAGE",
"INT", "INT",
) )
RETURN_NAMES = ( RETURN_NAMES = (
@@ -1025,6 +1042,8 @@ class DumasH3PlanExtractSceneImagesNode:
"image5", "image5",
"image6", "image6",
"image7", "image7",
"image8",
"image9",
"connected_images", "connected_images",
) )
FUNCTION = "extract" FUNCTION = "extract"
@@ -1063,11 +1082,11 @@ class DumasH3PlanExtractSceneImagesNode:
scene_index = _normalize_h3_scene_index(passthrough_plan, scene_index) scene_index = _normalize_h3_scene_index(passthrough_plan, scene_index)
token, _scene_counts = _h3_plan_binding_entry(passthrough_plan) token, _scene_counts = _h3_plan_binding_entry(passthrough_plan)
if not token: if not token:
return (passthrough_plan, None, None, None, None, None, None, None, 0) return (passthrough_plan, *(None for _ in range(_H3_PLAN_IMAGE_SLOTS)), 0)
registry = _H3_PLAN_IMAGE_BINDINGS.get(token) or {} registry = _H3_PLAN_IMAGE_BINDINGS.get(token) or {}
_touch_plan_image_binding(token) _touch_plan_image_binding(token)
images = registry.get(int(scene_index)) or (None, None, None, None, None, None, None) images = registry.get(int(scene_index)) or (None,) * _H3_PLAN_IMAGE_SLOTS
return (passthrough_plan, *images, _connected_image_count(images)) return (passthrough_plan, *images, _connected_image_count(images))
+34 -2
View File
@@ -524,6 +524,8 @@ class DumasImageNodeTests(unittest.TestCase):
image_1 = FakeTensorBatch() image_1 = FakeTensorBatch()
image_2 = FakeTensorBatch(width=12, height=9) image_2 = FakeTensorBatch(width=12, height=9)
image_3 = FakeTensorBatch(width=8, height=8) image_3 = FakeTensorBatch(width=8, height=8)
image_4 = FakeTensorBatch(width=16, height=16)
image_5 = FakeTensorBatch(width=20, height=12)
plan_after_first, _connected = attach_node.attach(plan=plan, scene_index=1, image2=image_1) plan_after_first, _connected = attach_node.attach(plan=plan, scene_index=1, image2=image_1)
plan_after_second, _connected = attach_node.attach( plan_after_second, _connected = attach_node.attach(
@@ -531,6 +533,8 @@ class DumasImageNodeTests(unittest.TestCase):
scene_index=2, scene_index=2,
image6=image_2, image6=image_2,
image7=image_3, image7=image_3,
image8=image_4,
image9=image_5,
) )
scene1 = extract_node.extract(plan_after_second, 1) scene1 = extract_node.extract(plan_after_second, 1)
@@ -540,10 +544,12 @@ class DumasImageNodeTests(unittest.TestCase):
self.assertEqual(scene1[-1], 1) self.assertEqual(scene1[-1], 1)
self.assertIs(scene2[6], image_2) self.assertIs(scene2[6], image_2)
self.assertIs(scene2[7], image_3) self.assertIs(scene2[7], image_3)
self.assertEqual(scene2[-1], 2) self.assertIs(scene2[8], image_4)
self.assertIs(scene2[9], image_5)
self.assertEqual(scene2[-1], 4)
self.assertEqual( self.assertEqual(
plan_after_second["_dumas_scene_image_bindings"]["scene_counts"], plan_after_second["_dumas_scene_image_bindings"]["scene_counts"],
{"1": 1, "2": 2}, {"1": 1, "2": 4},
) )
def test_h3_plan_scene_images_metadata_is_json_serializable(self): def test_h3_plan_scene_images_metadata_is_json_serializable(self):
@@ -559,6 +565,32 @@ class DumasImageNodeTests(unittest.TestCase):
self.assertEqual(connected, 1) self.assertEqual(connected, 1)
json.dumps(attached_plan) json.dumps(attached_plan)
def test_h3_plan_scene_images_support_nine_slots(self):
attach_node = self.image_nodes.DumasH3PlanAttachSceneImagesNode()
extract_node = self.image_nodes.DumasH3PlanExtractSceneImagesNode()
plan = {"shots": [{"id": "one"}]}
images = [FakeTensorBatch(width=8 + index, height=8 + index) for index in range(9)]
attached_plan, connected = attach_node.attach(
plan=plan,
scene_index=1,
image1=images[0],
image2=images[1],
image3=images[2],
image4=images[3],
image5=images[4],
image6=images[5],
image7=images[6],
image8=images[7],
image9=images[8],
)
extracted = extract_node.extract(attached_plan, 1)
self.assertEqual(connected, 9)
for index, image in enumerate(images, start=1):
self.assertIs(extracted[index], image)
self.assertEqual(extracted[-1], 9)
def test_h3_plan_scene_images_can_clear_a_scene_binding(self): def test_h3_plan_scene_images_can_clear_a_scene_binding(self):
attach_node = self.image_nodes.DumasH3PlanAttachSceneImagesNode() attach_node = self.image_nodes.DumasH3PlanAttachSceneImagesNode()
extract_node = self.image_nodes.DumasH3PlanExtractSceneImagesNode() extract_node = self.image_nodes.DumasH3PlanExtractSceneImagesNode()