Skip to content

Fix conversion of ComfyUI v3 (io.ComfyNode) nodes - #160

Open
fanfu70 wants to merge 1 commit into
pydn:mainfrom
fanfu70:main
Open

Fix conversion of ComfyUI v3 (io.ComfyNode) nodes#160
fanfu70 wants to merge 1 commit into
pydn:mainfrom
fanfu70:main

Conversation

@fanfu70

@fanfu70 fanfu70 commented Aug 26, 2026

Copy link
Copy Markdown

The planner only understood the legacy V1 node API and mis-converted v3 (comfy_api.io.ComfyNode) nodes in two ways:

  1. Dynamic inputs (e.g. the DynamicCombo on ResizeImageMaskNode) are sent by the frontend as dotted prompt keys ("resize_type" plus "resize_type.shorter_size"). The planner filtered inputs against the execute() signature, but v3 nodes expose EXECUTE_NORMALIZED(*args, **kwargs), so the filter was silently disabled and the dotted keys were emitted as literal kwargs. Generated scripts crashed with: TypeError: ResizeImageMaskNode.execute() got an unexpected keyword argument 'resize_type.shorter_size'

  2. Hidden inputs (UNIQUE_ID / PROMPT / EXTRA_PNGINFO) are not execute() kwargs for v3 nodes; the executor injects them on a per-node class clone (PREPARE_CLASS_CLONE -> cls.hidden). The planner instead injected fabricated kwargs such as unique_id=, which v3 execute() rejects, and left cls.hidden as None, crashing nodes that read hidden inputs (e.g. GetImageSize's cls.hidden.unique_id) in headless scripts.

Changes in comfyui_to_python/generator/planner.py:

  • Detect v3 nodes via FUNCTION ("EXECUTE_NORMALIZED" / "EXECUTE_NORMALIZED_ASYNC") or the presence of EXECUTE_NORMALIZED. Async v3 nodes raise a clear error: generated scripts are synchronous.
  • prepare_v3_node_call(): mirror comfy_execution.get_input_data() using ComfyUI's own machinery (comfy_api.latest._io):
    • get_finalized_class_inputs() expands dynamic schema entries against the live prompt keys,
    • build_nested_inputs() folds the dotted keys into the single nested-dict kwarg execute() actually receives, e.g. resize_type={"resize_type": "scale shorter dimension", "shorter_size": 1080}
    • stale dotted keys from unselected DynamicCombo options are dropped, and ACCEPT_ALL_INPUTS nodes pass extra inputs through as the executor does.
  • Render v3 calls as type(obj).PREPARE_CLASS_CLONE({...}).EXECUTE_NORMALIZED(...) so cls.hidden is populated exactly like in the server executor. The payload carries "PROMPT" / "EXTRA_PNGINFO" when declared; UNIQUE_ID and DYNPROMPT are intentionally omitted because a headless script has no PromptServer (HiddenHolder resolves them to None).
  • format_structured_value(): render prompt-derived list/dict literals (folded dynamic inputs, DICT widget values) as Python source, including link placeholders resolved inside nested dicts.

V1 nodes are untouched: signature filtering and unique_id / prompt / extra_pnginfo kwarg injection behave exactly as before (verified for **kwargs nodes such as VHS_LoadVideo / VHS_VideoCombine).

tests/runtime/generated/upscale-model-loader.py is regenerated with the fixed renderer (v3 clone-style calls; also picks up pre-existing drift in the inlined cleanup_comfyui_runtime helper from an older revision).

Verified:

  • Full existing unit suite passes (codegen regression, upscale export, project contracts, node runtime cleanup, runtime validation harness).
  • Regenerated a real LTXV video-enhancement workflow containing v3 nodes (ResizeImageMaskNode x2, GetImageSize and others): nested dicts and clone calls are correct, V1 rendering unchanged, output compiles.
  • Executed the generated v3 call lines verbatim against the real node classes with synthetic tensors: correct output shapes (short side == 1080; multiples of 32) and GetImageSize runs headless without a PromptServer; the old dotted-kwarg form still raises the original TypeError.

The planner only understood the legacy V1 node API and mis-converted v3
(comfy_api.io.ComfyNode) nodes in two ways:

1. Dynamic inputs (e.g. the DynamicCombo on ResizeImageMaskNode) are sent
   by the frontend as dotted prompt keys ("resize_type" plus
   "resize_type.shorter_size"). The planner filtered inputs against the
   execute() signature, but v3 nodes expose EXECUTE_NORMALIZED(*args,
   **kwargs), so the filter was silently disabled and the dotted keys were
   emitted as literal kwargs. Generated scripts crashed with:
       TypeError: ResizeImageMaskNode.execute() got an unexpected keyword
       argument 'resize_type.shorter_size'

2. Hidden inputs (UNIQUE_ID / PROMPT / EXTRA_PNGINFO) are not execute()
   kwargs for v3 nodes; the executor injects them on a per-node class clone
   (PREPARE_CLASS_CLONE -> cls.hidden). The planner instead injected
   fabricated kwargs such as unique_id=<random int>, which v3 execute()
   rejects, and left cls.hidden as None, crashing nodes that read hidden
   inputs (e.g. GetImageSize's cls.hidden.unique_id) in headless scripts.

Changes in comfyui_to_python/generator/planner.py:

- Detect v3 nodes via FUNCTION ("EXECUTE_NORMALIZED" /
  "EXECUTE_NORMALIZED_ASYNC") or the presence of EXECUTE_NORMALIZED. Async
  v3 nodes raise a clear error: generated scripts are synchronous.
- prepare_v3_node_call(): mirror comfy_execution.get_input_data() using
  ComfyUI's own machinery (comfy_api.latest._io):
    * get_finalized_class_inputs() expands dynamic schema entries against the
      live prompt keys,
    * build_nested_inputs() folds the dotted keys into the single nested-dict
      kwarg execute() actually receives, e.g.
        resize_type={"resize_type": "scale shorter dimension", "shorter_size": 1080}
    * stale dotted keys from unselected DynamicCombo options are dropped, and
      ACCEPT_ALL_INPUTS nodes pass extra inputs through as the executor does.
- Render v3 calls as type(obj).PREPARE_CLASS_CLONE({...}).EXECUTE_NORMALIZED(...)
  so cls.hidden is populated exactly like in the server executor. The payload
  carries "PROMPT" / "EXTRA_PNGINFO" when declared; UNIQUE_ID and DYNPROMPT
  are intentionally omitted because a headless script has no PromptServer
  (HiddenHolder resolves them to None).
- format_structured_value(): render prompt-derived list/dict literals (folded
  dynamic inputs, DICT widget values) as Python source, including link
  placeholders resolved inside nested dicts.

V1 nodes are untouched: signature filtering and unique_id / prompt /
extra_pnginfo kwarg injection behave exactly as before (verified for **kwargs
nodes such as VHS_LoadVideo / VHS_VideoCombine).

tests/runtime/generated/upscale-model-loader.py is regenerated with the fixed
renderer (v3 clone-style calls; also picks up pre-existing drift in the
inlined cleanup_comfyui_runtime helper from an older revision).

Verified:
- Full existing unit suite passes (codegen regression, upscale export, project
  contracts, node runtime cleanup, runtime validation harness).
- Regenerated a real LTXV video-enhancement workflow containing v3 nodes
  (ResizeImageMaskNode x2, GetImageSize and others): nested dicts and clone
  calls are correct, V1 rendering unchanged, output compiles.
- Executed the generated v3 call lines verbatim against the real node classes
  with synthetic tensors: correct output shapes (short side == 1080; multiples
  of 32) and GetImageSize runs headless without a PromptServer; the old
  dotted-kwarg form still raises the original TypeError.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant