diff --git a/.github/workflows/publish_action.yml b/.github/workflows/publish_action.yml new file mode 100644 index 0000000..a3b6568 --- /dev/null +++ b/.github/workflows/publish_action.yml @@ -0,0 +1,24 @@ +name: Publish to Comfy registry +on: + workflow_dispatch: + push: + branches: + - main + paths: + - "pyproject.toml" + +permissions: + issues: write + +jobs: + publish-node: + name: Publish Custom Node to registry + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'SparknightLLC' }} + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Publish Custom Node + uses: Comfy-Org/publish-node-action@v1 + with: + personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} ## Add your own personal access token to your Github Repository secrets and reference it here. diff --git a/README.md b/README.md new file mode 100644 index 0000000..63ab015 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# ComfyUI-WeightedRandomChoice + +A node for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) that picks from `input_a` and `input_b` based on the given `chance`. + +I'll be honest, I was a little surprised I couldn't find a node like this on the Comfy Registry. It's simple yet very useful for things like randomizing parts of a prompt. + +![workflow_weightedrandomchoice](workflows/workflow_weightedrandomchoice.png) + +### Installation + +Simply drag the image above into ComfyUI and use [ComfyUI Manager ยป Install Missing Custom Nodes](https://github.com/ltdrdata/ComfyUI-Manager). + +### Inputs + +- `input_a`: Supports any datatype. +- `input_b`: Supports any datatype. +- `chance`: A float between 0 and 1 that indicates the probability of selecting `input_a`. For example, set this to 0.95 for a 95% chance to return `input_a`. +- `seed`: Useful for locking in a certain result. + +### Output + +As a quality of life feature, you can provide only `input_a` or `input_b`, and if the unconnected input is chosen, the node will return a default value based on the datatype of the other input. + +For example, let's say you set `input_a` to 50 and leave `input_b` blank. If `input_b` is chosen, the node will return the default integer value of 0. For strings, it would return a blank string, and for floats, 0.0. \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..ccba6a3 --- /dev/null +++ b/__init__.py @@ -0,0 +1,92 @@ +# wildcard trick is taken from pythongossss's +class AnyType(str): + + def __ne__(self, __value: object) -> bool: + return False + + +def get_default_value(example_input): + if isinstance(example_input, str): + return "" + elif isinstance(example_input, int): + return 0 + elif isinstance(example_input, float): + return 0.0 + else: + return "" + + +class WeightedRandomChoiceNode: + """ + Takes `input_a` and `input_b` and makes a choice between them based on the outcome of `chance`. + """ + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "chance": ("FLOAT", { + "default": 0.5, + "min": 0.0, + "max": 1.0, + "step": 0.01, + }), + "seed": ("INT", { + "default": 0, + "min": 0, + "max": 0xffffffffffffffff, + "step": 1, + }), + }, + "optional": { + "input_a": (AnyType("*"), { + "default": "" + }), + "input_b": (AnyType("*"), { + "default": "" + }), + }, + } + + RETURN_TYPES = (AnyType("*"), ) + + FUNCTION = "run" + + def run(self, **kwargs): + """ + Returns a value based on the chance value. + """ + import random + + random.seed(kwargs.get("seed")) + + # Determine a default value based on the datatype of the inputs + if kwargs.get("input_a") is not None: + default_value = get_default_value(kwargs.get("input_a")) + elif kwargs.get("input_b") is not None: + default_value = get_default_value(kwargs.get("input_b")) + else: + return "" + + if kwargs.get("chance") >= random.random(): + input_a = kwargs.get("input_a") + return (default_value if input_a is None else input_a, ) + else: + input_b = kwargs.get("input_b") + return (default_value if input_b is None else input_b, ) + + +# Set the web directory, any .js file in that directory will be loaded by the frontend as a frontend extension +# WEB_DIRECTORY = "./somejs" + +# A dictionary that contains all nodes you want to export with their names +# NOTE: names should be globally unique +NODE_CLASS_MAPPINGS = { + "WeightedRandomChoice": WeightedRandomChoiceNode, +} + +# A dictionary that contains the friendly/humanly readable titles for the nodes +NODE_DISPLAY_NAME_MAPPINGS = {"WeightedRandomChoice": "Weighted Random Choice"} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bc65857 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +# pyproject.toml +[project] +name = "comfyui-weightedrandomchoice" # Unique identifier for your node. Immutable after creation. +description = "A node for ComfyUI that picks from `input_a` and `input_b` based on the given `chance`." +version = "0.0.1" # Custom Node version. Must be semantically versioned. +license = { file = "LICENSE.txt" } +dependencies = [] # Filled in from requirements.txt + +[project.urls] +Repository = "https://github.com/SparknightLLC/ComfyUI-WeightedRandomChoice" + +[tool.comfy] +PublisherId = "sparknight" # TODO (fill in Publisher ID from Comfy Registry Website). +DisplayName = "ComfyUI-WeightedRandomChoice" # Display name for the Custom Node. Can be changed later. +Icon = "https://example.com/icon.png" # SVG, PNG, JPG or GIF (MAX. 800x400px) \ No newline at end of file diff --git a/workflows/workflow_weightedrandomchoice.png b/workflows/workflow_weightedrandomchoice.png new file mode 100644 index 0000000..962123a Binary files /dev/null and b/workflows/workflow_weightedrandomchoice.png differ