ComfyUI icon indicating copy to clipboard operation
ComfyUI copied to clipboard

Multiple inputs

Open jn-jairo opened this issue 2 years ago • 10 comments

This PR allows inputs to have multiple connections.

It works by adding new inputs when you make the connection, this way there is always one free input to connect, in the frontend the names of the inputs are changed by appending [0], [1], ... to the name, later the backend merge those inputs in an array.

Example:

image

Code of the custom node used in the example:

class SelectItem:
    CATEGORY = "_for_testing"
    RETURN_TYPES = ("*",)
    FUNCTION = "run"

    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
                "only_active": (["true", "false"], ),
            },
            "optional": {
                "values": ("*", {"multiple": True}),
            },
        }

    def run(self, index, only_active, values=[]):
        selected = None

        if only_active == "true":
            values = [value for value in values if value is not None]

        index = index % len(values)

        selected = values[index]

        return (selected,)

NODE_CLASS_MAPPINGS = {
    "SelectItem": SelectItem,
}

NODE_DISPLAY_NAME_MAPPINGS = {
    "SelectItem": "Select Item",
}

It accepts multiple values and select one based on an index, in this case the index was transformed from widget to input and is using a random value, so each generation gets a random size.

P.S.: This example code uses the generic type from the #1566 but you get the idea.

jn-jairo avatar Sep 22 '23 06:09 jn-jairo

Very useful, please merge

BeatBroccoli avatar Jul 08 '24 11:07 BeatBroccoli

Very useful, please merge

again, this is already a solved thing inside the core. You just use KWARGS from python.

 @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
                "only_active": (["true", "false"], ),
            }
        }
        
  def run(self, index, only_active, **kw):
      selected = None

      if only_active == "true":
          values = [value for value in values if value is not None]

      index = index % len(values)
      key = kw.keys()[index]
      selected = kw[key]
      return (selected,)

Both things you asked to be pushed are: 10+ months old and require core changes but are not required if you actually know how to use python.

Amorano avatar Jul 08 '24 14:07 Amorano

Is there clear documentation on how to have multiple inputs inside core? I haven't found that anywhere, other than a link to Dynamic Inputs here: https://github.com/Suzie1/ComfyUI_Guide_To_Making_Custom_Nodes/wiki/js-extensions#dynamic-widgets

Is that the recommended workflow for handling multiple inputs in a single input?

shhlife avatar Jul 08 '24 18:07 shhlife

For the people asking to merge it, please be patient, it is an old code, I just came back to work with comfyui, I have to catch up with the news, I'll check my old PRs and see if they are needed with the current state of comfyui, a lot have changed since I wrote this PR, but if this is still a missing feature I'll see for it to work with the current version of comfyui.

jn-jairo avatar Jul 17 '24 08:07 jn-jairo

Is there clear documentation on how to have multiple inputs inside core? I haven't found that anywhere, other than a link to Dynamic Inputs here: https://github.com/Suzie1/ComfyUI_Guide_To_Making_Custom_Nodes/wiki/js-extensions#dynamic-widgets

Is that the recommended workflow for handling multiple inputs in a single input?

I didnt want to do a PR for the docs, but hopefully here is a clean, clear example:

https://github.com/cozy-comfyui/cozy_ex_dynamic

I cut it down to just a single type and indexing at 1.

I feel a few more of the mechanisms, especially on the JS side, could be done as one off examples.

HTH.

Amorano avatar Jul 18 '24 02:07 Amorano

Thanks for the link to the example!

i did give this a try, and unfortunately it didn't work when I had multiple inputs for the node that weren't dynamic - it started renaming things funky. Now that could be because I had other stuff in my js file that was messing with it.. but have you tried this with more inputs, or with dynamic inputs that come after previous inputs?

shhlife avatar Jul 20 '24 02:07 shhlife

@shhlife for now I updated my fork, the branch beta has all my changes https://github.com/jn-jairo/ComfyUI/tree/beta I'm working on an extension (custom nodes) to patch these changes into ComfyUI.

The code on the branch beta works well with the vanilla ComfyUI, but I cannot guarantee it will work with other extensions.

I have some custom nodes at https://github.com/jn-jairo/jn_node_suite_comfyui that use this feature, this repository is temporary, I'm working on another repository for the new version that is compatible with the new comfy register, but I use those nodes and they work fine, even in my most complex workflow.

jn-jairo avatar Jul 20 '24 04:07 jn-jairo

Thanks for the link to the example!

i did give this a try, and unfortunately it didn't work when I had multiple inputs for the node that weren't dynamic - it started renaming things funky. Now that could be because I had other stuff in my js file that was messing with it.. but have you tried this with more inputs, or with dynamic inputs that come after previous inputs?

Correct. The example itself is for only the dynamic mechanism, not adding statics to it. Basically a route node. It is only setup to be a single prefix/type for all the inputs.

I can potentially make another example, but the one that exists is only to that exact problem of making and using the dynamic input itself -- the user is free to employ that mechanism how they need (with other inputs, e.g.)

For the more complex examples with additional inputs, etc... I recommend looking over the solutions in mtb's repo, kijai's repo, my repo, et. al.

Amorano avatar Jul 20 '24 14:07 Amorano

FYI:

def run(self, index, only_active, values=[]):
        selected = None

In Python, using mutable default arguments like lists or dictionaries can lead to unexpected behavior. You should initialize those parameters (list, dict) to None and recast in the body:

def run(self, index, only_active, values=None):
        if values is None:
            values = []

Fredrik Lundh can explain it way better than I: https://web.archive.org/web/20200221224620id_/http://effbot.org/zone/default-values.htm

Amorano avatar Jul 20 '24 14:07 Amorano

FYI:

def run(self, index, only_active, values=[]):
        selected = None

In Python, using mutable default arguments like lists or dictionaries can lead to unexpected behavior. You should initialize those parameters (list, dict) to None and recast in the body:

def run(self, index, only_active, values=None):
        if values is None:
            values = []

Fredrik Lundh can explain it way better than I: https://web.archive.org/web/20200221224620id_/http://effbot.org/zone/default-values.htm

Python is a stupid language 🙄

jn-jairo avatar Jul 20 '24 22:07 jn-jairo

frontend is replaced by https://github.com/Comfy-Org/ComfyUI_frontend now

mcmonkey4eva avatar Sep 16 '24 04:09 mcmonkey4eva

Let's be honest, no one cared about this PR in this repo, and no one will care about it if I redo the PR in the other repo, so I won't waste my time.

jn-jairo avatar Sep 25 '24 17:09 jn-jairo

see reply at https://github.com/comfyanonymous/ComfyUI/pull/2015#issuecomment-2376050796

mcmonkey4eva avatar Sep 26 '24 06:09 mcmonkey4eva