From 825751af36400bb5225abd4eda0378cc0776e36f Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 12 Sep 2026 03:02:50 -0700 Subject: [PATCH] Add the String to Vec2 node --- node-graph/nodes/text/src/lib.rs | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/node-graph/nodes/text/src/lib.rs b/node-graph/nodes/text/src/lib.rs index 9faa704064b..3a7154d9166 100644 --- a/node-graph/nodes/text/src/lib.rs +++ b/node-graph/nodes/text/src/lib.rs @@ -394,6 +394,32 @@ fn string_to_number( Item::from_parts(string.trim().parse::().unwrap_or(*fallback.element()), attributes) } +/// Parses a string like `"3, 4.5"` into a Vec2, using a comma and/or whitespace as separators. Falls back to the chosen value if the string is not a valid pair of numbers. +#[node_macro::node(category("Text"), name("String to Vec2"))] +fn string_to_vec2( + _: impl Ctx, + /// The string containing two numbers separated by a comma or whitespace, like `"3, 4.5"`, optionally wrapped in `(`parentheses`)` or `[`square brackets`]`. Each number follows the same rules as the "String to Number" node. + string: Item, + /// The value of the result if the string cannot be parsed as a valid Vec2. + fallback: Item, +) -> Item { + let (string, attributes) = string.into_parts(); + + let trimmed = string.trim(); + let unwrapped = (trimmed.strip_prefix('(').and_then(|inner| inner.strip_suffix(')'))) + .or_else(|| trimmed.strip_prefix('[').and_then(|inner| inner.strip_suffix(']'))) + .unwrap_or(trimmed); + + // Exactly two numbers, so a longer list is not quietly truncated into a pair + let mut numbers = unwrapped.split(|c: char| c == ',' || c.is_whitespace()).filter(|piece| !piece.is_empty()).map(str::parse::); + let parsed = match (numbers.next(), numbers.next(), numbers.next()) { + (Some(Ok(x)), Some(Ok(y)), None) => DVec2::new(x, y), + _ => *fallback.element(), + }; + + Item::from_parts(parsed, attributes) +} + /// Removes leading and/or trailing whitespace from a string. Common whitespace characters include spaces, tabs, and newlines. #[node_macro::node(category("Text"))] fn string_trim( @@ -891,3 +917,22 @@ fn serialize(_: impl Ctx, #[implementations(String, bool, f Item::from_parts(result, attributes) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn string_to_vec2_accepts_pairs_and_falls_back_otherwise() { + let fallback = DVec2::new(-1., -1.); + let parse = |text: &str| string_to_vec2((), Item::new_from_element(text.to_string()), Item::new_from_element(fallback)).into_element(); + + for text in ["3, 4.5", "3 4.5", " 3,4.5 ", "(3, 4.5)", "[3, 4.5]", "3e0,\t4.5"] { + assert_eq!(parse(text), DVec2::new(3., 4.5), "{text:?} should parse"); + } + + for text in ["", "3", "3, 4, 5", "3, four", "(3, 4.5]"] { + assert_eq!(parse(text), fallback, "{text:?} should fall back"); + } + } +}