From f3dbb9735fca11eabc218d9e1f95e8d323c370ea Mon Sep 17 00:00:00 2001 From: ryanontheinstide Date: Sun, 5 Jan 2025 20:40:33 -0500 Subject: [PATCH] fix tooltip application for dynamic inputs, feature info node --- __init__.py | 6 +++++- nodes/flex/feature_extractors.py | 36 +++++++++++++++++++++++++++++++- nodes/flex/features.py | 2 ++ nodes/masks/flex_masks.py | 2 +- tooltips/tooltip_manager.py | 33 +++++++++++++++++++++-------- 5 files changed, 67 insertions(+), 12 deletions(-) diff --git a/__init__.py b/__init__.py index 266b046..265a1fd 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,6 @@ from .tooltips import TooltipManager, apply_tooltips from .tooltips.categories import register_all_tooltips # Register tooltips immediately after import -register_all_tooltips() from .node_configs.node_configs import CombinedMeta from collections import OrderedDict @@ -78,6 +77,7 @@ from .nodes.flex.feature_extractors import( ManualFeatureNode, ManualFeatureFromPipe, DrawableFeatureNode, + FeatureInfoNode, ) from .nodes.flex.feature_extractors_audio import( @@ -410,6 +410,7 @@ NODE_CLASS_MAPPINGS = { "LocationFromPoint": LocationFromPoint, "LocationTransform": LocationTransform, "AreaFeatureNode": AreaFeatureNode, + "FeatureInfoNode": FeatureInfoNode, "FeatureToWeightsStrategy": FeatureToWeightsStrategy, "FeatureToSplineData": FeatureToSplineData, @@ -593,3 +594,6 @@ if hasattr(PromptServer, "instance"): PromptServer.instance.app.add_routes( [web.static("/ryanontheinside_web_async", (Path(__file__).parent.absolute() / "ryanontheinside_web_async").as_posix())] ) + +#register tooltips after all classes are initialized +register_all_tooltips() diff --git a/nodes/flex/feature_extractors.py b/nodes/flex/feature_extractors.py index 06628d8..2a74cbb 100644 --- a/nodes/flex/feature_extractors.py +++ b/nodes/flex/feature_extractors.py @@ -20,10 +20,14 @@ class FeatureExtractorBase(RyanOnTheInside, ABC): @classmethod def INPUT_TYPES(cls): + print(f"\n[DEBUG] Getting INPUT_TYPES for {cls.__name__}") feature_class = cls.feature_type() + print(f"[DEBUG] Feature class: {feature_class.__name__}") + extraction_methods = feature_class.get_extraction_methods() + print(f"[DEBUG] Extraction methods: {extraction_methods}") return { "required": { - "extraction_method": (feature_class.get_extraction_methods(), {"default": feature_class.get_extraction_methods()[0]}), + "extraction_method": (extraction_methods, {"default": extraction_methods[0]}), "frame_rate": ("FLOAT", {"default": 30.0, "min": 1.0, "max": 120.0, "step": 0.1}), "frame_count": ("INT", {"default": 30, "min": 1}), "width": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 64}), @@ -392,7 +396,37 @@ class DrawableFeatureNode(FeatureExtractorBase): return (drawable_feature,) +@apply_tooltips +class FeatureInfoNode(RyanOnTheInside): + """ + Node that extracts common information from feature inputs. + """ + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "feature": ("FEATURE",), # Accepts any feature type + } + } + RETURN_TYPES = ("STRING", "STRING", "INT", "INT", "INT", "INT") + RETURN_NAMES = ("name", "type", "frame_rate", "frame_count", "width", "height") + FUNCTION = "get_info" + CATEGORY = "Ryan/Features" + + def get_info(self, feature): + """Extract common information from the feature""" + return ( + feature.name, + feature.type, + feature.frame_rate, + feature.frame_count, + feature.width if feature.width is not None else 0, + feature.height if feature.height is not None else 0 + ) diff --git a/nodes/flex/features.py b/nodes/flex/features.py index 286b6e2..6fcb766 100644 --- a/nodes/flex/features.py +++ b/nodes/flex/features.py @@ -7,6 +7,7 @@ from scipy.interpolate import interp1d, make_interp_spline class BaseFeature(ABC): def __init__(self, name, feature_type, frame_rate, frame_count, width, height): + print(f"\n[DEBUG] Initializing BaseFeature: {name} of type {feature_type}") self.name = name self.type = feature_type self.frame_rate = frame_rate @@ -25,6 +26,7 @@ class BaseFeature(ABC): @abstractmethod def get_extraction_methods(cls): """Return a list of parameter names that can be modulated.""" + print(f"\n[DEBUG] Getting extraction methods for {cls.__name__}") return [] def get_value_at_frame(self, frame_index): diff --git a/nodes/masks/flex_masks.py b/nodes/masks/flex_masks.py index 06a6894..ea134e3 100644 --- a/nodes/masks/flex_masks.py +++ b/nodes/masks/flex_masks.py @@ -12,7 +12,7 @@ from typing import List import torch.nn.functional as F from ...tooltips import apply_tooltips -@apply_tooltips +# @apply_tooltips class FlexMaskMorph(FlexMaskBase): @classmethod def INPUT_TYPES(cls): diff --git a/tooltips/tooltip_manager.py b/tooltips/tooltip_manager.py index fbc2b08..bc9d8d1 100644 --- a/tooltips/tooltip_manager.py +++ b/tooltips/tooltip_manager.py @@ -163,10 +163,10 @@ def apply_tooltips(node_class): @classmethod def input_types_with_tooltips(cls): try: - input_types = original_input_types() - if input_types is None: + input_types = original_input_types.__get__(cls, cls)() + if not isinstance(input_types, dict): return original_input_types.__get__(cls, cls)() - except Exception: + except Exception as e: return original_input_types.__get__(cls, cls)() tooltips = TooltipManager.get_tooltips(cls.__name__) @@ -174,17 +174,30 @@ def apply_tooltips(node_class): def add_tooltip_to_config(param_name, config): if param_name not in tooltips: return config - + tooltip = tooltips[param_name] # Handle tuple format (type, config_dict) if isinstance(config, tuple): - if len(config) == 2 and isinstance(config[1], dict): + if len(config) == 2: param_type, param_config = config - param_config = param_config.copy() - param_config["tooltip"] = tooltip - return (param_type, param_config) + # If param_config is already a dict, just add the tooltip + if isinstance(param_config, dict): + param_config = param_config.copy() + param_config["tooltip"] = tooltip + return (param_type, param_config) + # If param_type is a list, this is a dropdown without config + elif isinstance(param_type, list): + return (param_type, {"tooltip": tooltip}) + # If param_type is a tuple containing a method call result, preserve it + elif isinstance(param_type, tuple) and len(param_type) > 0: + return (param_type, {"tooltip": tooltip}) + # Otherwise treat second element as is + return config elif len(config) == 1: + # Handle single-element tuples that might be method call results + if isinstance(config[0], tuple): + return (config[0], {"tooltip": tooltip}) return (config[0], {"tooltip": tooltip}) return config @@ -213,8 +226,10 @@ def apply_tooltips(node_class): param_name: add_tooltip_to_config(param_name, config) for param_name, config in input_types["optional"].items() } - + return input_types + # Replace the original INPUT_TYPES with our wrapped version node_class.INPUT_TYPES = input_types_with_tooltips + return node_class \ No newline at end of file