From 02dafd74a1423fa6528523e76996b87d01ca78ce Mon Sep 17 00:00:00 2001 From: "Alexander G. Morano" Date: Mon, 24 Jun 2024 20:43:25 -0700 Subject: [PATCH] 1.2 --- core/calc.py | 69 ++++++++++++++++++++----------- core/compose.py | 11 ++--- core/create.py | 6 +-- core/utility.py | 6 +-- pyproject.toml | 2 +- sup/image.py | 13 +++--- sup/lexicon.py | 4 +- sup/util.py | 16 +++++--- web/nodes/array.js | 2 +- web/nodes/calc_binary.js | 44 ++------------------ web/nodes/lerp.js | 4 +- web/nodes/queue.js | 51 +++++++++++++---------- web/nodes/shape_gen.js | 56 +++++++++++++++++++++++++ web/nodes/value.js | 79 ++++++++++------------------------- web/util/util_jov.js | 89 +++++++++++++++++++++++----------------- web/util/util_widget.js | 34 +++++++++++---- 16 files changed, 261 insertions(+), 225 deletions(-) create mode 100644 web/nodes/shape_gen.js diff --git a/core/calc.py b/core/calc.py index 7348fe6..3cbb72e 100644 --- a/core/calc.py +++ b/core/calc.py @@ -321,10 +321,20 @@ def run(self, **kw) -> Tuple[bool]: pbar = ProgressBar(len(params)) for idx, (A, B, a_xyzw, b_xyzw, op, typ, flip) in enumerate(params): logger.debug(f'val {A}, {B}, {a_xyzw}, {b_xyzw}') + # [[0.008793391303918097, 0.008793391303918097, 0.008793391303918097]], [[0.008793391303918097, 0.008793391303918097, 0.008793391303918097]], (0, 0, 0, 0), (0, 0, 0, 0) typ = EnumConvertType[typ] + + size = min(3, max(0 if not isinstance(A, (list,)) else len(A), 0 if not isinstance(B, (list,)) else len(B))) + best_type = [EnumConvertType.FLOAT, EnumConvertType.VEC2, EnumConvertType.VEC3, EnumConvertType.VEC4][size] + val_a = parse_value(A, best_type, a_xyzw) + val_a = parse_value(val_a, EnumConvertType.VEC4, a_xyzw) + val_b = parse_value(B, best_type, b_xyzw) + val_b = parse_value(val_b, EnumConvertType.VEC4, b_xyzw) + val_a = parse_value(A, EnumConvertType.VEC4, A if A is not None else a_xyzw) val_b = parse_value(B, EnumConvertType.VEC4, B if B is not None else b_xyzw) logger.debug(f'val {val_a}, {val_b}') + # (0, 0, 0, 0), (0, 0, 0, 0) if flip: val_a, val_b = val_b, val_a size = max(1, int(typ.value / 10)) @@ -571,7 +581,7 @@ def INPUT_TYPES(cls) -> dict: "optional": { Lexicon.IN_A: (WILDCARD, {"tooltip": "Custom Start Point"}), Lexicon.IN_B: (WILDCARD, {"tooltip": "Custom End Point"}), - Lexicon.FLOAT: ("FLOAT", {"default": 0., "min": 0., "max": 1.0, + Lexicon.FLOAT: ("FLOAT", {"default": 0.5, "min": 0., "max": 1.0, "step": 0.001, "precision": 4, "round": 0.00001, "tooltip": "Blend Amount. 0 = full A, 1 = full B"}), Lexicon.EASE: (["NONE"] + EnumEase._member_names_, {"default": "NONE"}), @@ -593,11 +603,12 @@ def INPUT_TYPES(cls) -> dict: return Lexicon._parse(d, cls) def run(self, **kw) -> Tuple[Any, Any]: - A = parse_param(kw, Lexicon.IN_A, EnumConvertType.ANY, (0,0,0,0), 0, 1) - B = parse_param(kw, Lexicon.IN_B, EnumConvertType.ANY, (0,0,0,0), 0, 1) + A = parse_param(kw, Lexicon.IN_A, EnumConvertType.ANY, None) + B = parse_param(kw, Lexicon.IN_B, EnumConvertType.ANY, None) + print(A, B) a_xyzw = parse_param(kw, Lexicon.IN_A+Lexicon.IN_A, EnumConvertType.VEC4, (0, 0, 0, 0)) b_xyzw = parse_param(kw, Lexicon.IN_B+Lexicon.IN_B, EnumConvertType.VEC4, (1, 1, 1, 1)) - alpha = parse_param(kw, Lexicon.FLOAT,EnumConvertType.FLOAT, 0, 0, 1) + alpha = parse_param(kw, Lexicon.FLOAT,EnumConvertType.FLOAT, 0.5, 0, 1) op = parse_param(kw, Lexicon.EASE, EnumConvertType.STRING, "NONE") typ = parse_param(kw, Lexicon.TYPE, EnumConvertType.STRING, EnumNumberType.FLOAT.name) values = [] @@ -607,12 +618,11 @@ def run(self, **kw) -> Tuple[Any, Any]: # make sure we only interpolate between the longest "stride" we can size = min(3, max(0 if not isinstance(A, (list,)) else len(A), 0 if not isinstance(B, (list,)) else len(B))) best_type = [EnumConvertType.FLOAT, EnumConvertType.VEC2, EnumConvertType.VEC3, EnumConvertType.VEC4][size] - val_a = parse_value(A, EnumConvertType.VEC4, a_xyzw) - val_b = parse_value(B, EnumConvertType.VEC4, b_xyzw) + val_a = parse_value(A, best_type, a_xyzw) + val_a = parse_value(val_a, EnumConvertType.VEC4, a_xyzw) + val_b = parse_value(B, best_type, b_xyzw) + val_b = parse_value(val_b, EnumConvertType.VEC4, b_xyzw) alpha = parse_value(alpha, EnumConvertType.VEC4, alpha) - # val_a = parse_value(A, EnumConvertType.VEC4, A if A is not None else a_xyzw) - # val_b = parse_value(B, EnumConvertType.VEC4, B if B is not None else b_xyzw) - # alpha = parse_value(alpha, EnumConvertType.VEC4, alpha) typ = EnumConvertType[typ] size = max(1, int(typ.value / 10)) if size > 1: @@ -675,9 +685,7 @@ def run(self, **kw) -> Tuple[torch.Tensor, torch.Tensor]: params = list(zip_longest_fill(pA, pB, swap_x, x, swap_y, y, swap_z, z, swap_w, w)) results = [] pbar = ProgressBar(len(params)) - print(pA, pB, swap_x, x, swap_y, y, swap_z, z, swap_w, w) for idx, (pA, pB, swap_x, x, swap_y, y, swap_z, z, swap_w, w) in enumerate(params): - print(pA, pB, swap_x, x, swap_y, y, swap_z, z, swap_w, w) swap_x = EnumSwizzle[swap_x] swap_y = EnumSwizzle[swap_y] swap_z = EnumSwizzle[swap_z] @@ -789,6 +797,7 @@ class ValueNode(JOVBaseNode): DESCRIPTION = """ The Value Node supplies raw or default values for various data types, supporting vector input with components for X, Y, Z, and W. It also provides a string input option. """ + UPDATE = False @classmethod def INPUT_TYPES(cls) -> dict: @@ -835,6 +844,12 @@ def INPUT_TYPES(cls) -> dict: }) return Lexicon._parse(d, cls) + @classmethod + def IS_CHANGED(cls) -> float: + if cls.UPDATE: + return float("nan") + return super().IS_CHANGED() + def run(self, **kw) -> Tuple[bool]: raw = parse_param(kw, Lexicon.IN_A, EnumConvertType.ANY, None) r_x = parse_param(kw, Lexicon.X, EnumConvertType.FLOAT, None) @@ -843,7 +858,7 @@ def run(self, **kw) -> Tuple[bool]: r_w = parse_param(kw, Lexicon.W, EnumConvertType.FLOAT, None) typ = parse_param(kw, Lexicon.TYPE, EnumConvertType.STRING, EnumConvertType.BOOLEAN.name) xyzw = parse_param(kw, Lexicon.IN_A+Lexicon.IN_A, EnumConvertType.VEC4, (0, 0, 0, 0)) - seed = parse_param(kw, Lexicon.RANDOM, EnumConvertType.INT, 0, 0) + seed = parse_param(kw, Lexicon.SEED, EnumConvertType.INT, 0, 0) yyzw = parse_param(kw, Lexicon.IN_B+Lexicon.IN_B, EnumConvertType.VEC4, (1, 1, 1, 1)) x_str = parse_param(kw, Lexicon.STRING, EnumConvertType.STRING, "") params = list(zip_longest_fill(raw, r_x, r_y, r_z, r_w, typ, xyzw, seed, yyzw, x_str)) @@ -855,7 +870,8 @@ def run(self, **kw) -> Tuple[bool]: default2 = None if typ not in [EnumConvertType.STRING, EnumConvertType.LIST, \ EnumConvertType.DICT,\ - EnumConvertType.IMAGE, EnumConvertType.LATENT, EnumConvertType.ANY, EnumConvertType.MASK]: + EnumConvertType.IMAGE, EnumConvertType.LATENT, \ + EnumConvertType.ANY, EnumConvertType.MASK]: a, b, c, d = xyzw a2, b2, c2, d2 = yyzw default = (a if r_x is None else r_x, @@ -871,25 +887,30 @@ def run(self, **kw) -> Tuple[bool]: else EnumConvertType.VEC4INT # check if set to randomize.... - if seed != 0: - val = list(val) - val2 = list(val2) - default = list(default) - for i, x in enumerate(val): + self.UPDATE = False + if seed != 0 and isinstance(val, (tuple, list,)) and isinstance(val2, (tuple, list,)): + self.UPDATE = True + # val = list(val) if isinstance(val, (tuple, list,)) else [val] + # val2 = list(val2) if isinstance(val2, (tuple, list,)) else [val2] + for i in range(len(val)): mx = max(val[i], val2[i]) mn = min(val[i], val2[i]) - logger.debug(f"{i}, {x}, {mx}, {mn}") - if typ == EnumConvertType.VEC4: - val[i] = default[i] = mn + random.random() * (mx - mn) + if mn == mx: + val[i] = mn else: - val[i] = default[i] = random.randrange(mn, mx) + random.seed(seed) + if typ == EnumConvertType.VEC4: + val[i] = mn + random.random() * (mx - mn) + else: + logger.debug(f"{i}, {mx}, {mn}") + val[i] = random.randint(mn, mx) - extra = parse_value(val, typ, default) + extra = parse_value(val, typ, val) ret = [val] ret.extend(extra) results.append(ret) pbar.update_absolute(idx) - return *list(zip(*results)), + return [x for x in zip(*results)] class WaveGeneratorNode(JOVBaseNode): NAME = "WAVE GEN (JOV) 🌊" diff --git a/core/compose.py b/core/compose.py index ea55b16..d0a75c0 100644 --- a/core/compose.py +++ b/core/compose.py @@ -703,13 +703,10 @@ def INPUT_TYPES(cls) -> dict: def run(self, **kw) -> Tuple[torch.Tensor, torch.Tensor]: images = [] pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None) - print(pA[0].shape) pbar = ProgressBar(len(pA)) for idx, pA in enumerate(pA): pA = channel_solid(chan=EnumImageType.BGRA) if pA is None else tensor2cv(pA) - print(pA.shape) pA = image_mask_add(pA) - print(pA.shape) pA = [cv2tensor(x, True) for x in image_split(pA)] images.append(pA) pbar.update_absolute(idx) @@ -827,6 +824,10 @@ def run(self, **kw) -> Tuple[torch.Tensor, torch.Tensor]: if len(images) == 0: logger.warning("no images to stack") return + data = [] + for i in images: + data.extend(i) + images = [tensor2cv(i) for i in data] axis = parse_param(kw, Lexicon.AXIS, EnumConvertType.STRING, EnumOrientation.GRID.name)[0] stride = parse_param(kw, Lexicon.STEP, EnumConvertType.INT, 1)[0] @@ -834,10 +835,6 @@ def run(self, **kw) -> Tuple[torch.Tensor, torch.Tensor]: wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, (512, 512), MIN_IMAGE_SIZE)[0] sample = parse_param(kw, Lexicon.SAMPLE, EnumConvertType.STRING, EnumInterpolation.LANCZOS4.name)[0] matte = parse_param(kw, Lexicon.MATTE, EnumConvertType.VEC4INT, (0, 0, 0, 255), 0, 255)[0] - data = [] - for i in images: - data.extend(i) - images = [tensor2cv(i) for i in data] axis = EnumOrientation[axis] img = image_stack(images, axis, stride) #, matte) mode = EnumScaleMode[mode] diff --git a/core/create.py b/core/create.py index b664299..980b498 100644 --- a/core/create.py +++ b/core/create.py @@ -128,8 +128,7 @@ def INPUT_TYPES(cls) -> dict: def run(self, **kw) -> Tuple[torch.Tensor, torch.Tensor]: shape = parse_param(kw, Lexicon.SHAPE, EnumConvertType.STRING, EnumShapes.CIRCLE.name) - print(kw[Lexicon.SIDES]) - sides = parse_param(kw, Lexicon.SIDES, EnumConvertType.INT, 3, 3, 512) + sides = parse_param(kw, Lexicon.SIDES, EnumConvertType.INT, 3, 3, 100) angle = parse_param(kw, Lexicon.ANGLE, EnumConvertType.FLOAT, 0) edge = parse_param(kw, Lexicon.EDGE, EnumConvertType.STRING, EnumEdge.CLIP.name) offset = parse_param(kw, Lexicon.XY, EnumConvertType.VEC2, (0, 0)) @@ -146,10 +145,7 @@ def run(self, **kw) -> Tuple[torch.Tensor, torch.Tensor]: sizeX, sizeY = size edge = EnumEdge[edge] shape = EnumShapes[shape] - #color = pixel_eval(color, EnumImageType.BGRA) - #matte = pixel_eval(matte, EnumImageType.BGRA) alpha_m = int(matte[3]) - print(sides) match shape: case EnumShapes.SQUARE: pA = shape_quad(width, height, sizeX, sizeX, fill=color[:3], back=matte[:3]) diff --git a/core/utility.py b/core/utility.py index f45e80f..d358c30 100644 --- a/core/utility.py +++ b/core/utility.py @@ -399,11 +399,9 @@ def run(self, ident, **kw) -> Tuple[torch.Tensor]: self.__history = [] longest_edge = 0 dynamic = parse_dynamic(kw, Lexicon.UNKNOWN, EnumConvertType.FLOAT, 0) - # each of the plugs + dynamic = [i[0] for i in dynamic] self.__ax.clear() for idx, val in enumerate(dynamic): - logger.debug(idx) - logger.debug(val) if isinstance(val, (set, tuple,)): val = list(val) if not isinstance(val, (list, )): @@ -412,7 +410,7 @@ def run(self, ident, **kw) -> Tuple[torch.Tensor]: self.__history.append([]) self.__history[idx].extend(val) if slice > 0: - stride = max(1, -slice + len(self.__history[idx]) + 1) + stride = max(0, -slice + len(self.__history[idx]) + 1) longest_edge = max(longest_edge, stride) self.__history[idx] = self.__history[idx][stride:] self.__ax.plot(self.__history[idx], color="rgbcymk"[idx]) diff --git a/pyproject.toml b/pyproject.toml index cb2bf57..9c19f08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "jovimetrix" description = "Compose like Substance Designer. Webcams, Media Streams (in/out), Tick animation, Color correction, Geometry manipulation, Pixel shader, Polygonal shape generator, Remap images gometry and color, Heavily inspired by WAS and MTB Node Suites." -version = "1.1.1" +version = "1.2" license = "MIT" dependencies = [ "aenum<4,>=3.1.15", diff --git a/sup/image.py b/sup/image.py index fe9a088..c639715 100644 --- a/sup/image.py +++ b/sup/image.py @@ -348,15 +348,17 @@ def cv2pil(image: TYPE_IMAGE) -> Image.Image: def cv2tensor(image: TYPE_IMAGE, mask:bool=False) -> torch.Tensor: """Convert a CV2 image to a torch tensor.""" if mask or len(image.shape) == 2: - image = image_mask(image) - image = image[:,:,0][:,:] - # image = np.expand_dims(image, -1) - return torch.from_numpy(image.astype(np.float32) / 255.0).unsqueeze(0) + image = image_grayscale(image) + ret = torch.from_numpy(image.astype(np.float32) / 255.0).unsqueeze(0) + if mask: + ret = ret.squeeze(-1) + return ret def cv2tensor_full(image: TYPE_IMAGE, matte:TYPE_PIXEL=0) -> Tuple[torch.Tensor, ...]: mask = image_mask(image) mask = mask[:,:,0][:,:] mask = torch.from_numpy(mask.astype(np.float32) / 255.0).unsqueeze(0) + # image = image_matte(image, matte) rgb = image_convert(image, 3) image = torch.from_numpy(image.astype(np.float32) / 255.0).unsqueeze(0) @@ -1373,7 +1375,7 @@ def image_stack(image_list: List[TYPE_IMAGE], axis:EnumOrientation=EnumOrientati height = max(height, h) images.append(i) count += 1 - print('stride:', stride) + images = [image_matte(image_convert(i, 4), matte, width, height) for i in images] matte = pixel_convert(matte, 4) match axis: @@ -1386,7 +1388,6 @@ def image_stack(image_list: List[TYPE_IMAGE], axis:EnumOrientation=EnumOrientati rows = [] for i in range(0, count, stride): - print(i) row = images[i:i + stride] row_stacked = np.hstack(row) rows.append(row_stacked) diff --git a/sup/lexicon.py b/sup/lexicon.py index e4e0efd..a3eb695 100644 --- a/sup/lexicon.py +++ b/sup/lexicon.py @@ -196,9 +196,9 @@ class Lexicon(metaclass=LexiconMeta): SEED = 'seed', "Random generator's initial value" SEGMENT = 'SEGMENT', "Number of parts which the input image should be split" SELECT = 'SELECT', "Select" - SHAPE = 'πŸ‡ΈπŸ‡΄', "Circle, Square or Polygonal forms" + SHAPE = 'SHAPE', "Circle, Square or Polygonal forms" SHIFT = 'SHIFT', "Shift" - SIDES = '♾️', "Number of sides polygon has (3-100)" + SIDES = 'SIDES', "Number of sides polygon has (3-100)" SIMULATOR = 'SIMULATOR', "Solver to use when translating to new color space" SIZE = 'πŸ“', "Scalar by which to scale the input" SKIP = 'SKIP', "Interval between segments" diff --git a/sup/util.py b/sup/util.py index db4f4c3..8ed89ba 100644 --- a/sup/util.py +++ b/sup/util.py @@ -7,6 +7,7 @@ import json import math from enum import Enum +from re import L from typing import Any, List, Generator, Optional, Tuple import numpy as np @@ -75,15 +76,14 @@ def parse_dynamic(data:dict, prefix:str, typ:EnumConvertType, default: Any) -> L found = None for k in keys: if k.startswith(f"{i}_") or k.startswith(f"{i}_{prefix}_"): - found = k + val = parse_param(data, k, typ, default) + vals.append(val) + found = True break if found is None: fail += 1 - continue - val = parse_param(data, found, typ, default) - vals.append(val) return vals def parse_value(val:Any, typ:EnumConvertType, default: Any, @@ -141,7 +141,7 @@ def parse_value(val:Any, typ:EnumConvertType, default: Any, if v == 0: v = zero except Exception as e: - logger.exception(e) + # logger.exception(e) logger.error(f"Error converting value: {e}") v = 0 new_val.append(v) @@ -241,10 +241,14 @@ def parse_param(data:dict, key:str, typ:EnumConvertType, default: Any, elif isinstance(val, (list, tuple, set)): if len(val) == 0: val = [None] + if isinstance(val, (tuple, set,)): + val = list(val) elif issubclass(type(val), (Enum,)): val = [str(val.name)] if typ == EnumConvertType.ANY: - return [val] + if not isinstance(val, (list, tuple, set)): + return [val] + return val if not isinstance(val, (list,)): val = [val] return [parse_value(v, typ, default, clip_min, clip_max, zero) for v in val] diff --git a/web/nodes/array.js b/web/nodes/array.js index be382da..84caeea 100644 --- a/web/nodes/array.js +++ b/web/nodes/array.js @@ -25,7 +25,7 @@ app.registerExtension({ const widget_idx = this.widgets.find(w => w.name === 'INDEX'); const widget_range = this.widgets.find(w => w.name === 'RANGE'); const widget_str = this.widgets.find(w => w.name === 'πŸ“'); - const widget_seed = this.widgets.find(w => w.name === 'seed'); + const widget_seed = this.widgets.find(w => w.name === 'SEED'); const widget_mode = this.widgets.find(w => w.name === 'MODE'); const widget_count = this.widgets.find(w => w.name === 'COUNT'); widget_mode.callback = async () => { diff --git a/web/nodes/calc_binary.js b/web/nodes/calc_binary.js index 74c3ad5..97cb90a 100644 --- a/web/nodes/calc_binary.js +++ b/web/nodes/calc_binary.js @@ -5,8 +5,8 @@ */ import { app } from "../../../scripts/app.js" -import { fitHeight, TypeSlot } from '../util/util.js' -import { widget_hide, process_value, widget_type_name, show_vector, show_boolean } from '../util/util_widget.js' +import { TypeSlot } from '../util/util.js' +import { hook_widget_AB } from '../util/util_jov.js' const _id = "OP BINARY (JOV) 🌟" @@ -20,45 +20,7 @@ app.registerExtension({ const onNodeCreated = nodeType.prototype.onNodeCreated nodeType.prototype.onNodeCreated = function () { const me = onNodeCreated?.apply(this) - const widget_x4 = this.widgets.find(w => w.name === 'πŸ…°οΈπŸ…°οΈ'); - const widget_y4 = this.widgets.find(w => w.name === 'πŸ…±οΈπŸ…±οΈ'); - widget_x4.options.menu = false; - widget_y4.options.menu = false; - let bool_x = {0:false} - let bool_y = {0:false} - let track_xyzw = {0:0, 1:0, 2:0, 3:0}; - let track_yyzw = {0:0, 1:0, 2:0, 3:0}; - const widget_combo = this.widgets.find(w => w.name === '❓'); - widget_combo.callback = () => { - const data_x = (widget_combo.value === "BOOLEAN") ? bool_x : track_xyzw; - const data_y = (widget_combo.value === "BOOLEAN") ? bool_y : track_yyzw; - show_vector(widget_x4, data_x, widget_combo.value); - show_vector(widget_y4, data_y, widget_combo.value); - this.outputs[0].name = widget_type_name(widget_combo.value); - fitHeight(this); - } - - widget_x4.callback = (value) => { - if (widget_x4.type === "toggle") { - bool_x[0] = value; - } else { - Object.keys(widget_x4.value).forEach((key) => { - track_xyzw[key] = widget_x4.value[key]; - }); - } - } - - widget_y4.callback = () => { - if (widget_y4.type === "toggle") { - bool_y[0] = widget_y4.value; - } else { - Object.keys(widget_y4.value).forEach((key) => { - track_yyzw[key] = widget_y4.value[key]; - }); - } - } - setTimeout(() => { widget_combo.callback(); }, 10); - return me; + const ab_data = hook_widget_AB(this, '❓'); } const onConnectionsChange = nodeType.prototype.onConnectionsChange diff --git a/web/nodes/lerp.js b/web/nodes/lerp.js index 8ce78f2..af37588 100644 --- a/web/nodes/lerp.js +++ b/web/nodes/lerp.js @@ -6,8 +6,7 @@ */ import { app } from "../../../scripts/app.js" -import { fitHeight, TypeSlot } from '../util/util.js' -import { widget_type_name, show_vector } from '../util/util_widget.js' +import { widget_type_name } from '../util/util_widget.js' import { hook_widget_AB } from '../util/util_jov.js' const _id = "LERP (JOV) πŸ”°" @@ -31,5 +30,6 @@ app.registerExtension({ return me; } return nodeType; + } }) diff --git a/web/nodes/queue.js b/web/nodes/queue.js index dd12715..a033e78 100644 --- a/web/nodes/queue.js +++ b/web/nodes/queue.js @@ -9,6 +9,7 @@ import { app } from "../../../scripts/app.js"; import { ComfyWidgets } from "../../../scripts/widgets.js" import { api_cmd_jovian } from '../util/util_api.js' import { flashBackgroundColor } from '../util/util_fun.js' +import { fitHeight, TypeSlotEvent, TypeSlot } from '../util/util.js' const _id = "QUEUE (JOV) πŸ—ƒ" const _prefix = 'πŸ¦„' @@ -105,29 +106,35 @@ app.registerExtension({ } const onConnectionsChange = nodeType.prototype.onConnectionsChange; - nodeType.prototype.onConnectionsChange = function (side, slot, connected, link_info) + nodeType.prototype.onConnectionsChange = function (slotType, slot, event, link_info, data) + //side, slot, connected, link_info { - if (slot == 0 && side == 2) { - if (connected) { - const node = app.graph.getNodeById(link_info.target_id); - if (node === undefined) { - return; - } - const target = node.inputs[link_info.target_slot]; - if (target === undefined) { - return; - } - - const widget = node.widgets?.find(w => w.name === target.name); - if (widget === undefined) { - return; - } - this.outputs[0].name = widget.name; - if (widget?.origType == "combo" || widget.type == "COMBO") { - const values = widget.options.values; - // remove all connections that don't match the list? - this.widget_queue.value = values.join('\n'); - update_list(this); + // console.info(slotType, slot, event, link_info, data) + if (slotType === TypeSlot.Output && slot == 0) { + if (link_info){ + if (event === TypeSlotEvent.Connect) { + const node = app.graph.getNodeById(link_info.target_id); + if (node === undefined) { + return; + } + const target = node.inputs[link_info.target_slot]; + if (target === undefined) { + return; + } + + const widget = node.widgets?.find(w => w.name === target.name); + if (widget === undefined) { + return; + } + this.outputs[0].name = widget.name; + if (widget?.origType == "combo" || widget.type == "COMBO") { + const values = widget.options.values; + // remove all connections that don't match the list? + this.widget_queue.value = values.join('\n'); + update_list(this); + } + } else { + this.outputs[0].name = _prefix; } } else { this.outputs[0].name = _prefix; diff --git a/web/nodes/shape_gen.js b/web/nodes/shape_gen.js new file mode 100644 index 0000000..e51e7e9 --- /dev/null +++ b/web/nodes/shape_gen.js @@ -0,0 +1,56 @@ +/** + * File: shape_generator.js + * Project: Jovimetrix + * + */ + +import { app } from "../../../scripts/app.js" +import { fitHeight, TypeSlot } from '../util/util.js' +import { widget_hide, widget_show } from '../util/util_widget.js' + +const _id = "SHAPE GEN (JOV) ✨" + +app.registerExtension({ + name: 'jovimetrix.node.' + _id, + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name !== _id) { + return; + } + + const onNodeCreated = nodeType.prototype.onNodeCreated + nodeType.prototype.onNodeCreated = function () { + const me = onNodeCreated?.apply(this) + const sides = this.widgets.find(w => w.name === 'SIDES'); + const op = this.widgets.find(w => w.name === 'SHAPE'); + op.callback = () => { + widget_hide(this, sides, "-jovi"); + if (op.value == 'POLYGON') { + widget_show(sides); + } + fitHeight(this); + } + setTimeout(() => { op.callback(); }, 10); + return me; + } + + const onConnectionsChange = nodeType.prototype.onConnectionsChange + nodeType.prototype.onConnectionsChange = function (slotType, slot, event, link_info, data) { + //console.info(slot) + if (slotType === TypeSlot.Input && slot.name == 'SHAPE') { + const widget_combo = this.widgets.find(w => w.name === 'SHAPE'); + setTimeout(() => { widget_combo.callback(); }, 10); + } + return onConnectionsChange?.apply(this, arguments); + } + + const onExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = function (message) { + //console.info(message) + const widget_combo = this.widgets.find(w => w.name === 'SHAPE'); + if (widget_combo.value == 'SHAPE') { + setTimeout(() => { widget_combo.callback(); }, 10); + } + return onExecuted?.apply(this, arguments) + } + } +}) diff --git a/web/nodes/value.js b/web/nodes/value.js index c832869..b1bc2f4 100644 --- a/web/nodes/value.js +++ b/web/nodes/value.js @@ -5,8 +5,9 @@ */ import { app } from "../../../scripts/app.js" -import { fitHeight } from '../util/util.js' -import { widget_show, widget_hide, process_any, widget_type_name, show_vector } from '../util/util_widget.js' +import { hook_widget_AB } from '../util/util_jov.js' +import { fitHeight, TypeSlot } from '../util/util.js' +import { widget_hide, process_any, widget_type_name } from '../util/util_widget.js' const _id = "VALUE (JOV) 🧬" @@ -20,6 +21,7 @@ app.registerExtension({ const onNodeCreated = nodeType.prototype.onNodeCreated nodeType.prototype.onNodeCreated = function () { const me = onNodeCreated?.apply(this); + const widget_x4 = this.widgets.find(w => w.name === 'πŸ…°οΈπŸ…°οΈ'); const widget_y4 = this.widgets.find(w => w.name === 'πŸ…±οΈπŸ…±οΈ'); widget_x4.options.menu = false; @@ -27,58 +29,44 @@ app.registerExtension({ let bool_x = {0:false} let bool_y = {0:false} let track_xyzw = {0:0, 1:0, 2:0, 3:0}; - let track_yyzw = {0:0, 1:0, 2:0, 3:0}; + let track_yyzw = {0:1, 1:1, 2:1, 3:1}; const widget_rng = this.widgets.find(w => w.name === 'seed'); const widget_str = this.widgets.find(w => w.name === 'πŸ“'); + this.outputs[1].type = "*"; this.outputs[2].type = "*"; this.outputs[3].type = "*"; this.outputs[4].type = "*"; - const self = this; widget_str.options.menu = false; widget_str.origComputeSize = widget_str.computeSize; - const widget_combo = this.widgets.find(w => w.name === '❓'); - widget_combo.callback = () => { - function set_output(what) { - self.outputs[1].type = what; - self.outputs[2].type = what; - self.outputs[3].type = what; - self.outputs[4].type = what; - } - - widget_hide(this, widget_x4, "-jovi"); - widget_hide(this, widget_y4, "-jovi"); + const ab_data = hook_widget_AB(this, '❓'); + const old_callback = ab_data.combo.callback; + ab_data.combo.callback = () => { + old_callback?.apply(this); widget_hide(this, widget_str, "-jovi"); - //widget_hide(this, widget_rng, "-jovi"); widget_str.inputEl.className = "jov-hidden"; widget_str.computeSize = () => [0, -4]; - this.outputs[0].name = widget_type_name(widget_combo.value); - this.outputs[0].type = widget_combo.value; - - if (["LIST", "DICT", "STRING"].includes(widget_combo.value)) { - process_any(widget_str, widget_combo.value); + this.outputs[0].name = widget_type_name(ab_data.combo.value); + this.outputs[0].type = ab_data.combo.value; + let type = ab_data.combo.value; + if (["LIST", "DICT", "STRING"].includes(ab_data.combo.value)) { + process_any(widget_str, ab_data.combo.value); widget_str.inputEl.className = "comfy-multiline-input"; widget_str.computeSize = widget_str.origComputeSize; - set_output(widget_combo.value); } else { - let type = "FLOAT"; - if (widget_combo.value.endsWith("INT")) { + type = "FLOAT"; + if (ab_data.combo.value.endsWith("INT")) { type = "INT"; } - const data_x = (widget_combo.value === "BOOLEAN") ? bool_x : track_xyzw; - const data_y = (widget_combo.value === "BOOLEAN") ? bool_y : track_yyzw; - widget_show(widget_x4) - show_vector(widget_x4, data_x, widget_combo.value); - set_output(type); - if (widget_rng.value > 0) { - widget_show(widget_y4) - show_vector(widget_y4, data_y, widget_combo.value); - } } + this.outputs[1].type = type; + this.outputs[2].type = type; + this.outputs[3].type = type; + this.outputs[4].type = type; fitHeight(this); } @@ -102,26 +90,12 @@ app.registerExtension({ } } - widget_rng.callback = () => { - widget_hide(this, widget_y4, "-jovi"); - if (widget_rng.value > 0 && !["STRING", "DICT", "LIST"].includes(widget_combo.value)) { - widget_show(widget_y4) - show_vector(widget_y4, widget_combo.value); - } - fitHeight(this); - } - setTimeout(() => { - widget_combo.callback(); + ab_data.combo.callback(); }, 10); - setTimeout(() => { - widget_rng.callback(); - }, 10); - return me; } - /* const onConnectionsChange = nodeType.prototype.onConnectionsChange nodeType.prototype.onConnectionsChange = function (slotType, slot, event, link_info, data) { if (slotType === TypeSlot.Input) { @@ -131,14 +105,5 @@ app.registerExtension({ return onConnectionsChange?.apply(this, arguments); } - const onExecuted = nodeType.prototype.onExecuted; - nodeType.prototype.onExecuted = function(message) { - const me = onExecuted?.apply(this,arguments); - let values = message["text"].toString().map(Number); - this.outputs[1]["name"] = values[1] + " width" - this.outputs[2]["name"] = values[2] + " height" - this.outputs[3]["name"] = values[0] + " count" - return me; - }*/ } }) diff --git a/web/util/util_jov.js b/web/util/util_jov.js index 6eb23d0..42f7ca1 100644 --- a/web/util/util_jov.js +++ b/web/util/util_jov.js @@ -5,7 +5,7 @@ */ import { fitHeight } from './util.js' -import { widget_type_name, show_vector, widget_find, widget_hide, widget_show } from './util_widget.js' +import { show_vector, widget_find, widget_hide, widget_show } from './util_widget.js' export function hook_widget_size_mode(node, wh_hide=true) { const wh = widget_find(node.widgets, 'πŸ‡ΌπŸ‡­'); @@ -27,38 +27,6 @@ export function hook_widget_size_mode(node, wh_hide=true) { setTimeout(() => { mode.callback(); }, 20); } -export function hook_widget_AB2(node) { - const data = { - bool_x: {0:A.value[0] != 0}, - bool_y: {0:B.value[0] != 0}, - track_xyzw: initializeTrack(A), - track_yyzw: initializeTrack(B), - A: node.widgets.find(w => w.name === 'πŸ…°οΈπŸ…°οΈ'), - B: node.widgets.find(w => w.name === 'πŸ…±οΈπŸ…±οΈ') - } - data.A.options.menu = false; - data.B.options.menu = false; - data.A.callback = () => { - if (data.A.type === "toggle") { - data.bool_x[0] = data.A.value; - } else { - Object.keys(data.A.value).forEach((key) => { - data.track_xyzw[key] = data.A.value[key]; - }); - } - } - data.B.callback = () => { - if (data.B.type === "toggle") { - data.bool_y[0] = data.B.value; - } else { - Object.keys(data.B.value).forEach((key) => { - data.track_yyzw[key] = data.B.value[key]; - }); - } - } - return data; -} - export function hook_widget_AB(node, control_key) { const initializeBool = (value) => ({ 0: value[0] !== 0 }); const initializeTrack = (widget) => { @@ -103,10 +71,14 @@ export function hook_widget_AB(node, control_key) { }; combo.callback = () => { - const data_x = (combo.value === "BOOLEAN") ? data.bool_x : data.track_xyzw; - const data_y = (combo.value === "BOOLEAN") ? data.bool_y : data.track_yyzw; - show_vector(A, data_x, combo.value); - show_vector(B, data_y, combo.value); + widget_hide(node, A, "-jovi"); + widget_hide(node, B, "-jovi"); + if (["VEC2", "VEC2INT", "COORD2D", "VEC3", "VEC3INT", "VEC4", "VEC4INT", "BOOLEAN", "INT", "FLOAT"].includes(combo.value)) { + const data_x = (combo.value === "BOOLEAN") ? data.bool_x : data.track_xyzw; + const data_y = (combo.value === "BOOLEAN") ? data.bool_y : data.track_yyzw; + show_vector(A, data_x, combo.value); + show_vector(B, data_y, combo.value); + } fitHeight(node); } @@ -114,4 +86,45 @@ export function hook_widget_AB(node, control_key) { setCallback(A, data.bool_x, data.track_xyzw); setCallback(B, data.bool_y, data.track_yyzw); return data; -} \ No newline at end of file +} + +/* +const widget_x4 = this.widgets.find(w => w.name === 'πŸ…°οΈπŸ…°οΈ'); + const widget_y4 = this.widgets.find(w => w.name === 'πŸ…±οΈπŸ…±οΈ'); + widget_x4.options.menu = false; + widget_y4.options.menu = false; + let bool_x = {0:false} + let bool_y = {0:false} + let track_xyzw = {0:0, 1:0, 2:0, 3:0}; + let track_yyzw = {0:0, 1:0, 2:0, 3:0}; + const widget_combo = this.widgets.find(w => w.name === '❓'); + widget_combo.callback = () => { + const data_x = (widget_combo.value === "BOOLEAN") ? bool_x : track_xyzw; + const data_y = (widget_combo.value === "BOOLEAN") ? bool_y : track_yyzw; + show_vector(widget_x4, data_x, widget_combo.value); + show_vector(widget_y4, data_y, widget_combo.value); + this.outputs[0].name = widget_type_name(widget_combo.value); + fitHeight(this); + } + + widget_x4.callback = (value) => { + if (widget_x4.type === "toggle") { + bool_x[0] = value; + } else { + Object.keys(widget_x4.value).forEach((key) => { + track_xyzw[key] = widget_x4.value[key]; + }); + } + } + + widget_y4.callback = () => { + if (widget_y4.type === "toggle") { + bool_y[0] = widget_y4.value; + } else { + Object.keys(widget_y4.value).forEach((key) => { + track_yyzw[key] = widget_y4.value[key]; + }); + } + } + setTimeout(() => { widget_combo.callback(); }, 10); + return me;*/ \ No newline at end of file diff --git a/web/util/util_widget.js b/web/util/util_widget.js index dd44085..e6c77c0 100644 --- a/web/util/util_widget.js +++ b/web/util/util_widget.js @@ -4,6 +4,8 @@ * */ +import { app } from "../../../scripts/app.js" + const regex = /\d/; const my_map = { @@ -74,6 +76,7 @@ export function widget_remove_all(node) { } export function widget_hide(node, widget, suffix = '') { + //console.info(widget) if (widget.hidden || widget.type?.startsWith(CONVERTED_TYPE + suffix)) { return; } @@ -116,12 +119,14 @@ export function widget_show(widget) { widget.computeSize = widget.origComputeSize; delete widget.origComputeSize; } else { - widget.computeSize = (target_width) => [target_width, 20]; + delete widget.computeSize; // = (target_width) => [target_width, 20]; } if (widget?.origSerializeValue) { widget.serializeValue = widget.origSerializeValue; delete widget.origSerializeValue; + } else { + delete widget.serializeValue; } widget.hidden = false; @@ -139,6 +144,7 @@ export function show_boolean(widget_x) { } export function show_vector(widget, values={}, type=undefined, precision=6) { + widget_show(widget); if (["FLOAT", "INT"].includes(type)) { type = "VEC1"; } else if (type == "BOOLEAN") { @@ -244,6 +250,9 @@ export function convertToInput(node, widget, config) { } export function getHoveredWidget() { + if (typeof app === 'undefined') + return; + const node = app.canvas.node_over; if (!node.widgets) return; @@ -252,19 +261,26 @@ export function getHoveredWidget() { const x = graphPos[0] - node.pos[0]; const y = graphPos[1] - node.pos[1]; + let pos_y; for (const w of node.widgets) { let widgetWidth, widgetHeight; if (w.computeSize) { const sz = w.computeSize(); - widgetWidth = sz[0]; - widgetHeight = sz[1]; + widgetWidth = sz[0] || 0; + widgetHeight = sz[1] || 0; } else { - widgetWidth = w.width || node.size[0]; + widgetWidth = w.width || node.size[0] || 0; widgetHeight = LiteGraph.NODE_WIDGET_HEIGHT; } - console.info(w.name, w) - if (w.last_y !== undefined && x >= 6 && x <= widgetWidth - 12 && y >= w.last_y && y <= w.last_y + widgetHeight) { + if (pos_y === undefined) { + pos_y = w.last_y || 0; + }; + //console.info(w) + // console.info(w.last_y, x, y, pos_y) + if (widgetHeight > 0 && widgetWidth > 0 && w.last_y !== undefined && x >= 6 && x <= widgetWidth - 12 && y >= w.last_y && y <= w.last_y + widgetHeight) { + //if (widgetHeight > 0 && widgetWidth > 0 && x >= 6 && x <= widgetWidth - 12 && y >= pos_y && y <= pos_y + widgetHeight) + //{ return w; - } - } -} \ No newline at end of file + } + } +}