Skip to content

Commit

Permalink
check frontend version and switch conversion logic
Browse files Browse the repository at this point in the history
outputs have to be singular since comfy lists force per item iteration
  • Loading branch information
Amorano committed Feb 18, 2025
1 parent c0eab45 commit a6a9c69
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 71 deletions.
51 changes: 14 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,31 @@ The built-in help system will dynamically parse any nodes found at the ComfyUI A
If those nodes have descriptions written in HTML or Markdown, they will be converted into HTML for presentation in the panel.

<div align="center">
<img src="https://github.com/user-attachments/assets/075f6f9b-b27b-4a6f-84da-a6db486446ff" alt="Clicking Nodes for Help" width="512"/>
<img src="https://github.com/user-attachments/assets/075f6f9b-b27b-4a6f-84da-a6db486446ff" alt="Clicking Nodes for Help" width="384"/>
</div>

### NODE COLORS

You can colorize nodes via their `title background`, `node body` or `title text`. This can be done to the node's `category` as well, so you can quickly color groups of nodes.

<div align="center">
<img src="https://github.com/user-attachments/assets/8de9561a-b231-4a49-a63a-4fd5fdba64d9" alt="node color panel" width="512"/>
<img src="https://github.com/user-attachments/assets/8de9561a-b231-4a49-a63a-4fd5fdba64d9" alt="node color panel" width="256"/>
</div>

## UPDATES

**2024/02/17** @1.7.20:
* changed to newer conversion logic on frontend 1.10.3+ -- `VECTOR` types will auto-switch to conversion logic based on version
* Fix for `VALUE NODE` to properly output vector data -- node in deprecation mode
* Added explcit `VECTOR2 / INT`, `VECTOR3 / INT`, `VECTOR4 / INT` nodes for value input
* Restructure to remove old UX hide/show widget features for compatibility with frontend (1.10.3+)
* Added `BATCH` output to `TICK NODE` so you can get a normal comfyui list (top output) and a Jovimetrix list (BATCH)

<div align="center">
<img src="https://github.com/user-attachments/assets/8ed13e6a-218c-468a-a480-53ab55b04d21" alt="explicit vector node supports" width="640"/>
<img src="https://github.com/user-attachments/assets/4459855c-c4e6-4739-811e-a6c90aa5a90c" alt="TICK Node Batch Support Output" width="384"/>
</div>

**2024/02/14** @1.7.17:
* cleaning up image support before split into Jovi_Comp

Expand Down Expand Up @@ -127,41 +139,6 @@ You can colorize nodes via their `title background`, `node body` or `title text`
* regex entries default to [5] fields
* [issue #4](https://github.com/Amorano/Jovi_GLSL/issues/4) value in uniforms for `GLSL nodes` were cancelling values being set

**2024/02/04** @1.6.10:
* adjusted new default color config to old defaults

**2024/02/04** @1.6.0:
* added colorizer option for `title text`
* settings deprecated and removed from ComfyUI settings panel
* colorizer setting migrated to ComfyUI per user settings
* automatic contrast option for non-colored nodes
* all tooltips migrated to ComfyUI core tooltips

**2024/02/02** @1.2.67:
* fixed `GLSL` nodes missing from parse due to category sort

**2024/02/01** @1.2.66:
* patched `MIDI FILTER EZ` to work for all filters not just the first found
* cleanup pyproject for registry
* proper logo for registry

**2024/02/01** @1.2.63:
* Fixed `MIDI FILTER` to parse all filters for trigger
* Better `MIDI FILTER` ranges:
* * Single numbers: "1, 2" (equals)
* * Closed ranges: "5-10" (between inclusive)
* * Open ranges: "-100" (less than or equal to 100)
* * Open ranges: "50-" (greater than or equal to 50)
* * 1, 5-10, 2
* * * would check == 1, == 2 and 5 <= x <= 10

**2024/01/30** @1.2.62:
* `QUEUE` nodes can do URL loading of images

**2024/01/08** @1.2.61:
* living in the stupidest timeline where case-sensitivty matters for URLS in chrome...
* fixed stylesheet reference in help

# INSTALLATION

[Please see the wiki for advanced use of the environment variables used during startup](https://github.com/Amorano/Jovimetrix/wiki/B.-ASICS)
Expand Down
185 changes: 157 additions & 28 deletions core/calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ def __init__(self, *arg, **kw) -> None:
self.lin = []
self.fixed = []
self.trigger = []
self.batch = []

class BitSplitNode(JOVBaseNode):
NAME = "BIT SPLIT (JOV) ⭄"
Expand Down Expand Up @@ -788,13 +789,15 @@ def run(self, **kw) -> Tuple[torch.Tensor, ...]:
class TickNode(JOVBaseNode):
NAME = "TICK (JOV) ⏱"
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
RETURN_TYPES = ("INT", "FLOAT", "FLOAT", JOV_TYPE_ANY)
RETURN_NAMES = (Lexicon.VALUE, Lexicon.LINEAR, Lexicon.FPS, Lexicon.TRIGGER)
RETURN_TYPES = ("INT", "FLOAT", "FLOAT", JOV_TYPE_ANY, JOV_TYPE_ANY,)
RETURN_NAMES = (Lexicon.VALUE, Lexicon.LINEAR, Lexicon.FPS, Lexicon.TRIGGER, Lexicon.BATCH,)
OUTPUT_IS_LIST = (True, False, False, False, False,)
OUTPUT_TOOLTIPS = (
"Current value for the configured tick",
"Current value for the configured tick as ComfyUI List",
"Normalized tick value (0..1) based on BPM and Loop",
"Current 'frame' in the tick based on FPS setting",
"Based on the BPM settings, on beat hit, output the input at '⚡'"
"Based on the BPM settings, on beat hit, output the input at '⚡'",
"Current batch of values for the configured tick as standard list which works in other Jovimetrix nodes",
)
SORT = 50
DESCRIPTION = """
Expand Down Expand Up @@ -832,12 +835,6 @@ def INPUT_TYPES(cls) -> dict:
})
return Lexicon._parse(d)

"""
@classmethod
def IS_CHANGED(cls, **kw) -> float:
return float('nan')
"""

def __init__(self, *arg, **kw) -> None:
super().__init__(*arg, **kw)
# how many pulses we have done -- total unless reset
Expand Down Expand Up @@ -877,13 +874,14 @@ def run(self, ident, **kw) -> Tuple[int, float, float, Any]:
results.lin.append(float(lin))
results.fixed.append(float(fixed_step))
results.trigger.append(trigger)
results.batch.append(self.__frame)
if not hold:
self.__frame += step
pbar.update_absolute(idx)

if batch < 2:
comfy_message(ident, "jovi-tick", {"i": self.__frame})
return (results.frame, results.lin, results.fixed, results.trigger,)
return (results.frame, results.lin, results.fixed, results.trigger, results.batch,)

class ValueNode(JOVBaseNode):
NAME = "VALUE (JOV) 🧬"
Expand Down Expand Up @@ -1000,15 +998,14 @@ def run(self, **kw) -> Tuple[bool]:
pbar.update_absolute(idx)
if len(results) < 2:
return results[0]
return [x for x in zip(*results)]
return *list(zip(*results)),

class WaveGeneratorNode(JOVBaseNode):
NAME = "WAVE GEN (JOV) 🌊"
NAME_PRETTY = "WAVE GEN (JOV) 🌊"
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
RETURN_TYPES = ("FLOAT", "INT", )
RETURN_NAMES = (Lexicon.FLOAT, Lexicon.INT, )
OUTPUT_IS_LIST = (True, True, )
SORT = 90
DESCRIPTION = """
Produce waveforms like sine, square, or sawtooth with adjustable frequency, amplitude, phase, and offset. It's handy for creating oscillating patterns or controlling animation dynamics. This node emits both continuous floating-point values and integer representations of the generated waves.
Expand Down Expand Up @@ -1055,11 +1052,14 @@ def run(self, **kw) -> Tuple[float, int]:
return *list(zip(*results)),

class Vector2Node(JOVBaseNode):
NAME = "VECTOR2 INT (JOV)"
NAME = "VECTOR2 (JOV)"
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
RETURN_TYPES = ("VEC2", "VEC2INT", )
RETURN_NAMES = ("VEC2", "VEC2INT", )
OUTPUT_IS_LIST = (True, True, )
OUTPUT_TOOLTIPS = (
"Vector2 with float values",
"Vector2 with integer values",
)
SORT = 290
DESCRIPTION = """
Outputs a VEC2 or VEC2INT.
Expand All @@ -1070,8 +1070,8 @@ def INPUT_TYPES(cls) -> dict:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
"X": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01}),
"Y": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01}),
"X": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01, "tooltip": "1st channel value"}),
"Y": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01, "tooltip": "2nd channel value"}),
}
})
return Lexicon._parse(d)
Expand All @@ -1089,12 +1089,53 @@ def run(self, **kw) -> Tuple[Tuple[float, ...], Tuple[int, ...]]:
pbar.update_absolute(idx)
return *list(zip(*results)),

class Vector2IntNode(JOVBaseNode):
NAME = "VECTOR2INT (JOV)"
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
RETURN_TYPES = ("VEC2", "VEC2INT", )
RETURN_NAMES = ("VEC2", "VEC2INT", )
OUTPUT_TOOLTIPS = (
"Vector2 with float values",
"Vector2 with integer values",
)
SORT = 291
DESCRIPTION = """
Outputs a VEC2 or VEC2INT.
"""

@classmethod
def INPUT_TYPES(cls) -> dict:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
"X": ("INT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 1, "tooltip": "1st channel value"}),
"Y": ("INT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 1, "tooltip": "2nd channel value"}),
}
})
return Lexicon._parse(d)

def run(self, **kw) -> Tuple[Tuple[float, ...], Tuple[int, ...]]:
x = parse_param(kw, "X", EnumConvertType.INT, 0, -sys.maxsize, sys.maxsize)
y = parse_param(kw, "Y", EnumConvertType.INT, 0, -sys.maxsize, sys.maxsize)
results = []
params = list(zip_longest_fill(x, y))
pbar = ProgressBar(len(params))
for idx, (x, y) in enumerate(params):
x = round(x, 6)
y = round(y, 6)
results.append([(x, y,), (int(x), int(y),)])
pbar.update_absolute(idx)
return *list(zip(*results)),

class Vector3Node(JOVBaseNode):
NAME = "VECTOR3 INT (JOV)"
NAME = "VECTOR3 (JOV)"
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
RETURN_TYPES = ("VEC3", "VEC3INT", )
RETURN_NAMES = ("VEC3", "VEC3INT", )
OUTPUT_IS_LIST = (True, True, )
OUTPUT_TOOLTIPS = (
"Vector3 with float values",
"Vector3 with integer values",
)
SORT = 292
DESCRIPTION = """
Outputs a VEC3 or VEC3INT.
Expand All @@ -1105,9 +1146,9 @@ def INPUT_TYPES(cls) -> dict:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
"X": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01}),
"Y": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01}),
"Z": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01}),
"X": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01, "tooltip": "1st channel value"}),
"Y": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01, "tooltip": "2nd channel value"}),
"Z": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01, "tooltip": "3rd channel value"}),
}
})
return Lexicon._parse(d)
Expand All @@ -1127,12 +1168,56 @@ def run(self, **kw) -> Tuple[Tuple[float, ...], Tuple[int, ...]]:
pbar.update_absolute(idx)
return *list(zip(*results)),

class Vector3IntNode(JOVBaseNode):
NAME = "VECTOR3INT (JOV)"
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
RETURN_TYPES = ("VEC3", "VEC3INT", )
RETURN_NAMES = ("VEC3", "VEC3INT", )
OUTPUT_TOOLTIPS = (
"Vector3 with float values",
"Vector3 with integer values",
)
SORT = 293
DESCRIPTION = """
Outputs a VEC3 or VEC3INT.
"""

@classmethod
def INPUT_TYPES(cls) -> dict:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
"X": ("INT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 1, "tooltip": "1st channel value"}),
"Y": ("INT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 1, "tooltip": "2nd channel value"}),
"Z": ("INT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 1, "tooltip": "3rd channel value"}),
}
})
return Lexicon._parse(d)

def run(self, **kw) -> Tuple[Tuple[float, ...], Tuple[int, ...]]:
x = parse_param(kw, "X", EnumConvertType.INT, 0, -sys.maxsize, sys.maxsize)
y = parse_param(kw, "Y", EnumConvertType.INT, 0, -sys.maxsize, sys.maxsize)
z = parse_param(kw, "Z", EnumConvertType.INT, 0, -sys.maxsize, sys.maxsize)
results = []
params = list(zip_longest_fill(x, y, z))
pbar = ProgressBar(len(params))
for idx, (x, y, z) in enumerate(params):
x = round(x, 6)
y = round(y, 6)
z = round(z, 6)
results.append([(x, y, z,), (int(x), int(y), int(z),)])
pbar.update_absolute(idx)
return *list(zip(*results)),

class Vector4Node(JOVBaseNode):
NAME = "VECTOR4 INT (JOV)"
NAME = "VECTOR4 (JOV)"
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
RETURN_TYPES = ("VEC4", "VEC4INT", )
RETURN_NAMES = ("VEC4", "VEC4INT", )
OUTPUT_IS_LIST = (True, True, )
OUTPUT_TOOLTIPS = (
"Vector4 with float values",
"Vector4 with integer values",
)
SORT = 294
DESCRIPTION = """
Outputs a VEC4 or VEC4INT.
Expand All @@ -1143,10 +1228,10 @@ def INPUT_TYPES(cls) -> dict:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
"X": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01}),
"Y": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01}),
"Z": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01}),
"W": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01}),
"X": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01, "tooltip": "1st channel value"}),
"Y": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01, "tooltip": "2nd channel value"}),
"Z": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01, "tooltip": "3rd channel value"}),
"W": ("FLOAT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 0.01, "tooltip": "4th channel value"}),
}
})
return Lexicon._parse(d)
Expand All @@ -1168,6 +1253,50 @@ def run(self, **kw) -> Tuple[Tuple[float, ...], Tuple[int, ...]]:
pbar.update_absolute(idx)
return *list(zip(*results)),

class Vector4IntNode(JOVBaseNode):
NAME = "VECTOR4INT (JOV)"
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
RETURN_TYPES = ("VEC4", "VEC4INT", )
RETURN_NAMES = ("VEC4", "VEC4INT", )
OUTPUT_TOOLTIPS = (
"Vector4 with float values",
"Vector4 with integer values",
)
SORT = 295
DESCRIPTION = """
Outputs a VEC4 or VEC4INT.
"""

@classmethod
def INPUT_TYPES(cls) -> dict:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
"X": ("INT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 1, "tooltip": "1st channel value"}),
"Y": ("INT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 1, "tooltip": "2nd channel value"}),
"Z": ("INT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 1, "tooltip": "3rd channel value"}),
"W": ("INT", {"default": 0, "min": -sys.maxsize, "max": sys.maxsize, "step": 1, "tooltip": "4th channel value"}),
}
})
return Lexicon._parse(d)

def run(self, **kw) -> Tuple[Tuple[float, ...], Tuple[int, ...]]:
x = parse_param(kw, "X", EnumConvertType.INT, 0, -sys.maxsize, sys.maxsize)
y = parse_param(kw, "Y", EnumConvertType.INT, 0, -sys.maxsize, sys.maxsize)
z = parse_param(kw, "Z", EnumConvertType.INT, 0, -sys.maxsize, sys.maxsize)
w = parse_param(kw, "W", EnumConvertType.INT, 0, -sys.maxsize, sys.maxsize)
results = []
params = list(zip_longest_fill(x, y, z, w))
pbar = ProgressBar(len(params))
for idx, (x, y, z, w,) in enumerate(params):
x = round(x, 6)
y = round(y, 6)
z = round(z, 6)
w = round(w, 6)
results.append([(x, y, z, w,), (int(x), int(y), int(z), int(w),)])
pbar.update_absolute(idx)
return *list(zip(*results)),

'''
class ParameterNode(JOVBaseNode):
NAME = "PARAMETER (JOV) ⚙️"
Expand Down
9 changes: 6 additions & 3 deletions node_list.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,11 @@
"TICK (JOV) \u23f1": "A timer and frame counter, emitting pulses or signals based on time intervals",
"TRANSFORM (JOV) \ud83c\udfdd\ufe0f": "Apply various geometric transformations to images, including translation, rotation, scaling, mirroring, tiling and perspective projection",
"VALUE (JOV) \ud83e\uddec": "Supplies raw or default values for various data types, supporting vector input with components for X, Y, Z, and W",
"VECTOR2 INT (JOV)": "Outputs a VEC2 or VEC2INT",
"VECTOR3 INT (JOV)": "Outputs a VEC3 or VEC3INT",
"VECTOR4 INT (JOV)": "Outputs a VEC4 or VEC4INT",
"VECTOR2 (JOV)": "Outputs a VEC2 or VEC2INT",
"VECTOR2INT (JOV)": "Outputs a VEC2 or VEC2INT",
"VECTOR3 (JOV)": "Outputs a VEC3 or VEC3INT",
"VECTOR3INT (JOV)": "Outputs a VEC3 or VEC3INT",
"VECTOR4 (JOV)": "Outputs a VEC4 or VEC4INT",
"VECTOR4INT (JOV)": "Outputs a VEC4 or VEC4INT",
"WAVE GEN (JOV) \ud83c\udf0a": "Produce waveforms like sine, square, or sawtooth with adjustable frequency, amplitude, phase, and offset"
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "jovimetrix"
description = "Integrates Webcam, MIDI, Spout and GLSL shader support. Animation via tick. Parameter manipulation with wave generator. Math operations with Unary and Binary support. Value conversion for all major types (int, string, list, dict, Image, Mask). Shape mask generation, image stacking and channel ops, batch splitting, merging and randomizing, load images and video from anywhere, dynamic bus routing with a single node, export support for GIPHY, save output anywhere! flatten, crop, transform; check colorblindness, make stereogram or stereoscopic images, or liner interpolate values and more."
version = "1.7.17"
version = "1.7.20"
license = { file = "LICENSE" }
readme = "README.md"
authors = [{ name = "Alexander G. Morano", email = "[email protected]" }]
Expand Down
Loading

0 comments on commit a6a9c69

Please sign in to comment.