Add seventh H3 scene image slot

This commit is contained in:
2026-08-13 09:33:05 +00:00
parent a2a9ff90f6
commit f261a048b7
3 changed files with 40 additions and 17 deletions
+5 -5
View File
@@ -17,15 +17,15 @@
- Supports filename tokens including `%input%`, `%input2%`, `%date:yyyy-MM-dd%`, `%counter%`, `%width%`, `%height%`, and `%batch_num%`. - Supports filename tokens including `%input%`, `%input2%`, `%date:yyyy-MM-dd%`, `%counter%`, `%width%`, `%height%`, and `%batch_num%`.
- `Dumas H3 Plan Attach Scene Images` - `Dumas H3 Plan Attach Scene Images`
- Inputs: `plan`, `scene_index`, optional `image1`..`image6` - Inputs: `plan`, `scene_index`, optional `image1`..`image7`
- Outputs: `plan`, `connected_images` - Outputs: `plan`, `connected_images`
- Attaches up to six optional image sockets to one MiniMax H3 plan scene while keeping the plan JSON-serializable for the upstream archive system. - Attaches up to seven 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 six-image bundle. - Chain one node per scene that needs its own seven-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`..`image6`, `connected_images` - Outputs: `plan`, `image1`..`image7`, `connected_images`
- Reads back the six optional images for a selected MiniMax H3 plan scene, for example by connecting the current `clip_index`. - Reads back the seven optional images for a selected MiniMax H3 plan scene, for example by connecting the current `clip_index`.
- `Dumas JSON String to Object` - `Dumas JSON String to Object`
- Input: `json_string` - Input: `json_string`
+30 -10
View File
@@ -172,8 +172,16 @@ def _h3_plan_binding_entry(plan):
return token, counts return token, counts
def _scene_images_tuple(image1=None, image2=None, image3=None, image4=None, image5=None, image6=None): def _scene_images_tuple(
return (image1, image2, image3, image4, image5, image6) image1=None,
image2=None,
image3=None,
image4=None,
image5=None,
image6=None,
image7=None,
):
return (image1, image2, image3, image4, image5, image6, image7)
def _connected_image_count(images): def _connected_image_count(images):
@@ -489,7 +497,7 @@ class DumasSaveImageNode:
class DumasH3PlanAttachSceneImagesNode: class DumasH3PlanAttachSceneImagesNode:
DESCRIPTION = ( DESCRIPTION = (
"Attach up to six optional IMAGE sockets to one H3 Chain Plan scene " "Attach up to seven 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."
) )
@@ -501,7 +509,7 @@ class DumasH3PlanAttachSceneImagesNode:
@classmethod @classmethod
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
optional = {} optional = {}
for slot in range(1, 7): for slot in range(1, 8):
optional[f"image{slot}"] = ( optional[f"image{slot}"] = (
"IMAGE", "IMAGE",
{ {
@@ -530,7 +538,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 six image sockets." "scene that needs up to seven image sockets."
), ),
}, },
), ),
@@ -548,10 +556,11 @@ class DumasH3PlanAttachSceneImagesNode:
image4=None, image4=None,
image5=None, image5=None,
image6=None, image6=None,
image7=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) images = _scene_images_tuple(image1, image2, image3, image4, image5, image6, image7)
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)
@@ -582,11 +591,21 @@ class DumasH3PlanAttachSceneImagesNode:
class DumasH3PlanExtractSceneImagesNode: class DumasH3PlanExtractSceneImagesNode:
DESCRIPTION = ( DESCRIPTION = (
"Read back the six optional image bindings for one H3 Chain Plan scene. " "Read back the seven 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."
) )
RETURN_TYPES = (_H3_PLAN_TYPE, "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "INT") RETURN_TYPES = (
_H3_PLAN_TYPE,
"IMAGE",
"IMAGE",
"IMAGE",
"IMAGE",
"IMAGE",
"IMAGE",
"IMAGE",
"INT",
)
RETURN_NAMES = ( RETURN_NAMES = (
"plan", "plan",
"image1", "image1",
@@ -595,6 +614,7 @@ class DumasH3PlanExtractSceneImagesNode:
"image4", "image4",
"image5", "image5",
"image6", "image6",
"image7",
"connected_images", "connected_images",
) )
FUNCTION = "extract" FUNCTION = "extract"
@@ -633,11 +653,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, 0) return (passthrough_plan, None, None, None, None, None, None, None, 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) images = registry.get(int(scene_index)) or (None, None, None, None, None, None, None)
return (passthrough_plan, *images, _connected_image_count(images)) return (passthrough_plan, *images, _connected_image_count(images))
+5 -2
View File
@@ -348,12 +348,14 @@ class DumasImageNodeTests(unittest.TestCase):
plan = {"shots": [{"id": "one"}, {"id": "two"}]} plan = {"shots": [{"id": "one"}, {"id": "two"}]}
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)
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(
plan=plan_after_first, plan=plan_after_first,
scene_index=2, scene_index=2,
image6=image_2, image6=image_2,
image7=image_3,
) )
scene1 = extract_node.extract(plan_after_second, 1) scene1 = extract_node.extract(plan_after_second, 1)
@@ -362,10 +364,11 @@ class DumasImageNodeTests(unittest.TestCase):
self.assertIs(scene1[2], image_1) self.assertIs(scene1[2], image_1)
self.assertEqual(scene1[-1], 1) self.assertEqual(scene1[-1], 1)
self.assertIs(scene2[6], image_2) self.assertIs(scene2[6], image_2)
self.assertEqual(scene2[-1], 1) self.assertIs(scene2[7], image_3)
self.assertEqual(scene2[-1], 2)
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": 1}, {"1": 1, "2": 2},
) )
def test_h3_plan_scene_images_metadata_is_json_serializable(self): def test_h3_plan_scene_images_metadata_is_json_serializable(self):