From 711d7995b30869c04dbda664c321c387b88acd5c Mon Sep 17 00:00:00 2001 From: matei jordache Date: Thu, 26 Mar 2026 19:15:02 -0700 Subject: [PATCH] node leaf sorting --- backend/node_menu.py | 7 +++---- backend/node_registry.py | 2 +- backend/nodes/analysis.py | 14 +++++++------- backend/nodes/display.py | 16 ++++++++-------- backend/nodes/filters.py | 10 +++++----- backend/nodes/io.py | 16 ++++++++-------- backend/nodes/level.py | 6 +++--- backend/nodes/mask.py | 10 +++++----- backend/nodes/modify.py | 6 +++--- backend/nodes/particle.py | 2 +- 10 files changed, 44 insertions(+), 45 deletions(-) diff --git a/backend/node_menu.py b/backend/node_menu.py index 45108b7..2bc2de8 100644 --- a/backend/node_menu.py +++ b/backend/node_menu.py @@ -86,14 +86,13 @@ for category, class_names in MENU_LAYOUT.items(): } -def get_menu_metadata(class_name: str, fallback_category: str = "uncategorized") -> dict[str, Any]: +def get_menu_metadata(class_name: str) -> dict[str, Any]: metadata = _NODE_METADATA.get(class_name) if metadata is not None: return dict(metadata) - fallback_order = _CATEGORY_ORDER.get(fallback_category, len(_CATEGORY_ORDER)) return { - "category": fallback_category, - "category_order": fallback_order, + "category": "Unsorted", + "category_order": len(_CATEGORY_ORDER), "menu_order": 10_000, } diff --git a/backend/node_registry.py b/backend/node_registry.py index 8b2a861..022d661 100644 --- a/backend/node_registry.py +++ b/backend/node_registry.py @@ -39,7 +39,7 @@ def get_node_info(class_name: str) -> dict[str, Any]: """ cls = NODE_CLASS_MAPPINGS[class_name] input_types: dict = cls.INPUT_TYPES() - menu_metadata = get_menu_metadata(class_name, getattr(cls, "CATEGORY", "uncategorized")) + menu_metadata = get_menu_metadata(class_name) return { "name": class_name, diff --git a/backend/nodes/analysis.py b/backend/nodes/analysis.py index 9bb9f67..839592f 100644 --- a/backend/nodes/analysis.py +++ b/backend/nodes/analysis.py @@ -33,7 +33,7 @@ class Statistics: RETURN_TYPES = ("MEASURE_TABLE",) RETURN_NAMES = ("stats",) FUNCTION = "process" - CATEGORY = "analysis" + DESCRIPTION = ( "Compute basic surface statistics: min, max, mean, RMS roughness, median, " "and skewness. Equivalent to gwy_data_field_get_min/max/avg/rms." @@ -82,7 +82,7 @@ class Histogram: RETURN_TYPES = ("MEASURE_TABLE", "COORDPAIR",) RETURN_NAMES = ("measurements", "marker pair",) FUNCTION = "process" - CATEGORY = "analysis" + DESCRIPTION = ( "Compute the height distribution histogram (DH). " "Use log scale to reveal small peaks next to a dominant background. " @@ -185,7 +185,7 @@ class Cursors: RETURN_TYPES = ("MEASURE_TABLE","COORDPAIR",) RETURN_NAMES = ("measurement","coord pair",) FUNCTION = "process" - CATEGORY = "analysis" + DESCRIPTION = ( "Place two cursors on a line plot or 2D field. " "On lines it reports x/y positions and dx/dy. " @@ -353,7 +353,7 @@ class FFT2D: RETURN_TYPES = ("DATA_FIELD", "DATA_FIELD", "DATA_FIELD", "DATA_FIELD") RETURN_NAMES = ("log_magnitude", "magnitude", "phase", "psdf") FUNCTION = "process" - CATEGORY = "analysis" + DESCRIPTION = ( "Compute the 2D FFT with optional windowing and mean/plane subtraction. " "Outputs log magnitude, magnitude, phase, and PSDF as separate channels. " @@ -511,7 +511,7 @@ class InverseFFT2D: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("image",) FUNCTION = "process" - CATEGORY = "analysis" + DESCRIPTION = ( "Reconstruct a spatial-domain image from a 2D frequency spectrum. " "For exact reconstruction, connect magnitude/phase (or log magnitude/phase, " @@ -658,7 +658,7 @@ class CrossSection: RETURN_TYPES = ("LINE", "COORDPAIR",) RETURN_NAMES = ("profile", "marker pair",) FUNCTION = "process" - CATEGORY = "analysis" + DESCRIPTION = ( "Extract a cross-section profile along a line between two points. " "Drag the markers on the image to set the line endpoints. " @@ -1010,7 +1010,7 @@ class Stats: RETURN_TYPES = ("FLOAT",) RETURN_NAMES = ("value",) FUNCTION = "process" - CATEGORY = "analysis" + DESCRIPTION = ( "Compute a contextual scalar statistic from a LINE, record table, DATA_FIELD, or IMAGE. " "The available operations adapt to the connected input type." diff --git a/backend/nodes/display.py b/backend/nodes/display.py index 0c263ba..69fbe68 100644 --- a/backend/nodes/display.py +++ b/backend/nodes/display.py @@ -348,7 +348,7 @@ class ColorMap: RETURN_TYPES = ("COLORMAP",) RETURN_NAMES = ("colormap",) FUNCTION = "build" - CATEGORY = "display" + DESCRIPTION = ( "Build a reusable colormap. Choose a preset, or create a custom gradient with min/max colours " "and any number of intermediate stops." @@ -389,7 +389,7 @@ class Font: RETURN_TYPES = ("FONT",) RETURN_NAMES = ("font",) FUNCTION = "build" - CATEGORY = "display" + DESCRIPTION = ( "Build a reusable font spec for annotation overlays. Choose a discovered system font, " "use the default fallback stack, or point to a custom font file." @@ -429,7 +429,7 @@ class Annotations: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("annotated",) FUNCTION = "render" - CATEGORY = "display" + DESCRIPTION = ( "Attach optional publication-style annotations to a DATA_FIELD without flattening the raw data. " "The preview shows a scale bar and/or side colour legend, while downstream field operations keep the underlying AFM values." @@ -488,7 +488,7 @@ class Markup: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("annotated",) FUNCTION = "process" - CATEGORY = "display" + DESCRIPTION = ( "Draw simple vector markup over a DATA_FIELD without flattening the underlying data. " "Choose a shape mode, colour, and stroke width, then drag directly on the preview to place lines, rectangles, circles, or arrows." @@ -549,7 +549,7 @@ class PreviewImage: RETURN_TYPES = () FUNCTION = "preview" - CATEGORY = "display" + OUTPUT_NODE = True DESCRIPTION = "Display an IMAGE or DATA_FIELD as a coloured thumbnail. Connect either input." @@ -614,7 +614,7 @@ class View3D: RETURN_TYPES = () FUNCTION = "render" - CATEGORY = "display" + OUTPUT_NODE = True DESCRIPTION = ( "Interactive 3D surface view of a DATA_FIELD. " @@ -691,7 +691,7 @@ class PrintTable: RETURN_TYPES = () FUNCTION = "print_table" - CATEGORY = "display" + OUTPUT_NODE = True DESCRIPTION = "Send a measurement or record table to the browser as a WebSocket message for display." @@ -724,7 +724,7 @@ class ValueDisplay: RETURN_TYPES = ("FLOAT",) RETURN_NAMES = ("value",) FUNCTION = "display_value" - CATEGORY = "display" + DESCRIPTION = "Display a FLOAT, or a selected numeric row from a measurement table, and pass the value through unchanged." _broadcast_value_fn = None diff --git a/backend/nodes/filters.py b/backend/nodes/filters.py index ceaf745..f4aa850 100644 --- a/backend/nodes/filters.py +++ b/backend/nodes/filters.py @@ -34,7 +34,7 @@ class GaussianFilter: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("filtered",) FUNCTION = "process" - CATEGORY = "filters" + DESCRIPTION = "Apply a Gaussian blur. Equivalent to gwy_data_field_filter_gaussian." def process(self, field: DataField, sigma: float) -> tuple: @@ -61,7 +61,7 @@ class MedianFilter: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("filtered",) FUNCTION = "process" - CATEGORY = "filters" + DESCRIPTION = "Apply a median filter. Equivalent to gwy_data_field_filter_median." def process(self, field: DataField, size: int) -> tuple: @@ -90,7 +90,7 @@ class EdgeDetect: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("edges",) FUNCTION = "process" - CATEGORY = "filters" + DESCRIPTION = ( "Detect edges using Sobel, Prewitt, Laplacian, or LoG operators. " "Equivalent to gwy_data_field_filter_sobel / gwy_data_field_filter_laplacian." @@ -229,7 +229,7 @@ class FFTFilter1D: RETURN_TYPES = ("LINE",) RETURN_NAMES = ("filtered",) FUNCTION = "process" - CATEGORY = "filters" + DESCRIPTION = ( "Frequency-domain filtering of a 1-D line profile. " "Supports lowpass, highpass, bandpass, and notch (band-reject) modes " @@ -295,7 +295,7 @@ class FFTFilter2D: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("filtered",) FUNCTION = "process" - CATEGORY = "filters" + DESCRIPTION = ( "Frequency-domain filtering of a 2-D data field. " "Supports lowpass, highpass, bandpass, and notch (band-reject) modes " diff --git a/backend/nodes/io.py b/backend/nodes/io.py index fc1a806..e7afd46 100644 --- a/backend/nodes/io.py +++ b/backend/nodes/io.py @@ -147,7 +147,7 @@ class Image: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("field",) FUNCTION = "load" - CATEGORY = "io" + DESCRIPTION = ( "Load any supported file. " "SPM formats (.gwy, .sxm, .ibw) provide calibrated dimensions; " @@ -384,7 +384,7 @@ class ImageDemo: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("field",) FUNCTION = "load" - CATEGORY = "io" + DESCRIPTION = "Load a bundled demo file so you can try the app without providing your own data." def load(self, name: str = "", colormap: str = "viridis", colormap_map=None): @@ -408,7 +408,7 @@ class Folder: RETURN_TYPES = ("DIRECTORY",) RETURN_NAMES = ("directory",) FUNCTION = "list_files" - CATEGORY = "io" + DESCRIPTION = ( "Pick a folder and output its directory path plus one file socket per compatible image, array, or SPM file inside it. " "Supported files include common images, .npy/.npz arrays, and .gwy/.sxm/.ibw scans." @@ -441,7 +441,7 @@ class Coordinate: RETURN_TYPES = ("COORD",) RETURN_NAMES = ("point",) FUNCTION = "process" - CATEGORY = "io" + DESCRIPTION = "Output a fractional (x, y) coordinate pair in [0, 1]." def process(self, x: float, y: float) -> tuple: @@ -464,7 +464,7 @@ class CoordinatePair: RETURN_TYPES = ("COORDPAIR",) RETURN_NAMES = ("coord pair",) FUNCTION = "process" - CATEGORY = "io" + DESCRIPTION = "Output a pair of coordinates." def process(self, a: tuple, b: tuple) -> tuple: @@ -490,7 +490,7 @@ class Number: RETURN_TYPES = ("FLOAT",) RETURN_NAMES = ("value",) FUNCTION = "process" - CATEGORY = "io" + DESCRIPTION = ( "Output a fixed numeric value. " "When connected to FLOAT inputs the exact value is used; " @@ -528,7 +528,7 @@ class RangeSlider: RETURN_TYPES = ("FLOAT",) RETURN_NAMES = ("value",) FUNCTION = "process" - CATEGORY = "io" + DESCRIPTION = ( "Interactive float slider. Set min and max bounds, then drag the slider to output a FLOAT value." ) @@ -584,7 +584,7 @@ class SaveImage: RETURN_TYPES = () FUNCTION = "save" - CATEGORY = "io" + OUTPUT_NODE = True MANUAL_TRIGGER = True DESCRIPTION = ( diff --git a/backend/nodes/level.py b/backend/nodes/level.py index 7c3736c..f254289 100644 --- a/backend/nodes/level.py +++ b/backend/nodes/level.py @@ -33,7 +33,7 @@ class PlaneLevelField: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("leveled",) FUNCTION = "process" - CATEGORY = "level" + DESCRIPTION = ( "Fit and subtract a least-squares plane from the data. " "Equivalent to gwy_data_field_fit_plane + gwy_data_field_plane_level." @@ -83,7 +83,7 @@ class PolyLevelField: RETURN_TYPES = ("DATA_FIELD", "DATA_FIELD") RETURN_NAMES = ("leveled", "background") FUNCTION = "process" - CATEGORY = "level" + DESCRIPTION = ( "Fit and subtract a polynomial background of given degree in x and y. " "Equivalent to gwy_data_field_fit_polynom." @@ -131,7 +131,7 @@ class FixZero: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("zeroed",) FUNCTION = "process" - CATEGORY = "level" + DESCRIPTION = ( "Shift data so that the minimum (or mean/median) is zero. " "Equivalent to fix_zero in Gwyddion's level.c." diff --git a/backend/nodes/mask.py b/backend/nodes/mask.py index a370e1e..e92ef63 100644 --- a/backend/nodes/mask.py +++ b/backend/nodes/mask.py @@ -173,7 +173,7 @@ class DrawMask: RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("mask",) FUNCTION = "process" - CATEGORY = "mask" + DESCRIPTION = ( "Paint a binary mask directly over an image preview. " "Pen size controls newly drawn strokes, the overlay lets you clear the mask, " @@ -227,7 +227,7 @@ class ThresholdMask: RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("mask",) FUNCTION = "process" - CATEGORY = "mask" + DESCRIPTION = ( "Create a binary mask by thresholding data. " "Otsu automatically finds the optimal threshold. " @@ -295,7 +295,7 @@ class MaskMorphology: RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("mask",) FUNCTION = "process" - CATEGORY = "mask" + DESCRIPTION = ( "Apply morphological operations to a binary mask. " "Dilate expands regions, erode shrinks them, " @@ -358,7 +358,7 @@ class MaskInvert: RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("mask",) FUNCTION = "process" - CATEGORY = "mask" + DESCRIPTION = "Invert a binary mask — swap masked and unmasked regions." _broadcast_fn = None @@ -400,7 +400,7 @@ class MaskCombine: RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("mask",) FUNCTION = "process" - CATEGORY = "mask" + DESCRIPTION = ( "Combine two binary masks with a boolean operation. " "AND keeps overlap, OR merges, XOR keeps non-overlapping regions, " diff --git a/backend/nodes/modify.py b/backend/nodes/modify.py index 39527ed..bb29a3f 100644 --- a/backend/nodes/modify.py +++ b/backend/nodes/modify.py @@ -30,7 +30,7 @@ class ColormapAdjust: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("field",) FUNCTION = "process" - CATEGORY = "modify" + DESCRIPTION = ( "Adjust how a DATA_FIELD maps into its colormap without changing the underlying data. " "offset and scale operate in normalized display coordinates; Auto resets to the full data range." @@ -71,7 +71,7 @@ class CropResizeField: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("field",) FUNCTION = "process" - CATEGORY = "modify" + DESCRIPTION = ( "Crop a DATA_FIELD with a draggable rectangle defined by two corners, then optionally resize it. " "Incoming COORD inputs can lock either corner. Cropping updates physical extents and offsets; " @@ -212,7 +212,7 @@ class RotateField: RETURN_TYPES = ("DATA_FIELD",) RETURN_NAMES = ("field",) FUNCTION = "process" - CATEGORY = "modify" + DESCRIPTION = ( "Rotate a DATA_FIELD counterclockwise by an angle in degrees. " "Optionally expand the canvas to keep the full rotated field while preserving the field center." diff --git a/backend/nodes/particle.py b/backend/nodes/particle.py index 48fe1ad..f6c3726 100644 --- a/backend/nodes/particle.py +++ b/backend/nodes/particle.py @@ -30,7 +30,7 @@ class ParticleAnalysis: RETURN_TYPES = ("RECORD_TABLE",) RETURN_NAMES = ("particle_stats",) FUNCTION = "process" - CATEGORY = "particles" + DESCRIPTION = ( "Label connected particle regions in a binary mask and compute per-particle " "statistics: area, equivalent diameter, mean/max height, bounding box. "