From ac1dce39ba41b5aa67686003c50b88e95e00b010 Mon Sep 17 00:00:00 2001 From: "daniil.loban" Date: Thu, 23 Oct 2025 03:08:05 +0300 Subject: [PATCH 1/6] Add two nodes for substring --- node-graph/gcore/src/logic.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 84842e0000..e9a3360194 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -120,3 +120,20 @@ async fn switch( if_false.eval(ctx).await } } + +// Get an indexed part of string whitch separated a specified delimeter ("1;2;3" e.t.c.) +#[node_macro::node(category("Text"))] +fn substring_by_index(_: impl Ctx, #[implementations(String)] string: String, delimeter: String, index: u32) -> String { + let idx = index as usize; + let parts: Vec<&str> = string.split(&delimeter).collect(); + if idx < parts.len() { parts[idx].to_string() } else { String::new() } +} + +// Get amount substrings like ";" in string (useful for check max index in substring_by_index) +#[node_macro::node(category("Text"))] +fn count_substring(_: impl Ctx, #[implementations(String)] string: String, substring: String) -> f64 { + if substring.is_empty() { + return 0.0; + } + string.matches(&substring).count() as f64 +} From 1f819fd3013ec62052f05e098f476df9a538ae65 Mon Sep 17 00:00:00 2001 From: "daniil.loban" Date: Thu, 23 Oct 2025 03:40:11 +0300 Subject: [PATCH 2/6] fix: number convention --- node-graph/gcore/src/logic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index e9a3360194..18722a20f5 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -133,7 +133,7 @@ fn substring_by_index(_: impl Ctx, #[implementations(String)] string: String, de #[node_macro::node(category("Text"))] fn count_substring(_: impl Ctx, #[implementations(String)] string: String, substring: String) -> f64 { if substring.is_empty() { - return 0.0; + return 0.; } string.matches(&substring).count() as f64 } From 6fc9f0f0543167d2de9370f4bf4af0aab876f883 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Mon, 27 Oct 2025 17:08:41 +0100 Subject: [PATCH 3/6] Remove unnecessay allocations and add support for \n in patterns --- node-graph/gcore/src/logic.rs | 31 +++++++++++++--------------- node-graph/node-macro/src/codegen.rs | 10 ++++++++- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 18722a20f5..0b978c92fb 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -69,6 +69,20 @@ fn string_length(_: impl Ctx, string: String) -> f64 { string.chars().count() as f64 } +// Get an indexed part of string whitch separated a specified delimeter ("1;2;3" e.t.c.) +#[node_macro::node(category("Text"))] +fn substring_by_index(_: impl Ctx, string: String, #[default("\\n")] delimeter: String, index: u32) -> String { + let delimeter = delimeter.replace("\\n", "\n"); + string.split(&delimeter).nth(index as usize).unwrap_or("").to_owned() +} + +// Get amount substrings like ";" in string (useful for check max index in substring_by_index) +#[node_macro::node(category("Text"))] +fn count_substring(_: impl Ctx, string: String, #[default("\\n")] substring: String) -> f64 { + let substring = substring.replace("\\n", "\n"); + string.matches(&substring).count() as f64 +} + /// Evaluates either the "If True" or "If False" input branch based on whether the input condition is true or false. #[node_macro::node(category("Math: Logic"))] async fn switch( @@ -120,20 +134,3 @@ async fn switch( if_false.eval(ctx).await } } - -// Get an indexed part of string whitch separated a specified delimeter ("1;2;3" e.t.c.) -#[node_macro::node(category("Text"))] -fn substring_by_index(_: impl Ctx, #[implementations(String)] string: String, delimeter: String, index: u32) -> String { - let idx = index as usize; - let parts: Vec<&str> = string.split(&delimeter).collect(); - if idx < parts.len() { parts[idx].to_string() } else { String::new() } -} - -// Get amount substrings like ";" in string (useful for check max index in substring_by_index) -#[node_macro::node(category("Text"))] -fn count_substring(_: impl Ctx, #[implementations(String)] string: String, substring: String) -> f64 { - if substring.is_empty() { - return 0.; - } - string.matches(&substring).count() as f64 -} diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index ad1e079bcd..d0733e1bb6 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -88,7 +88,15 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn .iter() .map(|field| match &field.ty { ParsedFieldType::Regular(RegularParsedField { value_source, .. }) => match value_source { - ParsedValueSource::Default(data) => quote!(RegistryValueSource::Default(stringify!(#data))), + ParsedValueSource::Default(data) => { + // Check if the data is a string literal by parsing the token stream + let data_str = data.to_string(); + if data_str.starts_with('"') && data_str.ends_with('"') && data_str.len() >= 2 { + quote!(RegistryValueSource::Default(#data)) + } else { + quote!(RegistryValueSource::Default(stringify!(#data))) + } + } ParsedValueSource::Scope(data) => quote!(RegistryValueSource::Scope(#data)), _ => quote!(RegistryValueSource::None), }, From c6ef366050d442c89071a07b47c3b58c90ef64b7 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sat, 1 Nov 2025 10:54:37 +0100 Subject: [PATCH 4/6] Make split node return Vec and remove count_elements node --- node-graph/gcore/src/graphic.rs | 1 + node-graph/gcore/src/logic.rs | 13 +++---------- node-graph/gcore/src/vector/vector_nodes.rs | 21 +++++++++++++++++++-- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/node-graph/gcore/src/graphic.rs b/node-graph/gcore/src/graphic.rs index d6774c8c67..31a0f6979a 100644 --- a/node-graph/gcore/src/graphic.rs +++ b/node-graph/gcore/src/graphic.rs @@ -451,6 +451,7 @@ fn index( Vec, Vec, Vec, + Vec, Table, Table, Table, diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 0b978c92fb..04442b99e5 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -69,18 +69,11 @@ fn string_length(_: impl Ctx, string: String) -> f64 { string.chars().count() as f64 } -// Get an indexed part of string whitch separated a specified delimeter ("1;2;3" e.t.c.) +// Spliet a string by a specified delimeter ("1;2;3" e.t.c.) and return a vector of strings #[node_macro::node(category("Text"))] -fn substring_by_index(_: impl Ctx, string: String, #[default("\\n")] delimeter: String, index: u32) -> String { +fn split(_: impl Ctx, string: String, #[default("\\n")] delimeter: String) -> Vec { let delimeter = delimeter.replace("\\n", "\n"); - string.split(&delimeter).nth(index as usize).unwrap_or("").to_owned() -} - -// Get amount substrings like ";" in string (useful for check max index in substring_by_index) -#[node_macro::node(category("Text"))] -fn count_substring(_: impl Ctx, string: String, #[default("\\n")] substring: String) -> f64 { - let substring = substring.replace("\\n", "\n"); - string.matches(&substring).count() as f64 + string.split(&delimeter).map(|x| x.to_string()).collect() } /// Evaluates either the "If True" or "If False" input branch based on whether the input condition is true or false. diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 309fa615da..e84db01a9c 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -1954,11 +1954,28 @@ fn point_inside(_: impl Ctx, source: Table, point: DVec2) -> bool { source.into_iter().any(|row| row.element.check_point_inside_shape(row.transform, point)) } +trait Count { + fn count(&self) -> usize; +} +impl Count for Table { + fn count(&self) -> usize { + self.len() + } +} +impl Count for Vec { + fn count(&self) -> usize { + self.len() + } +} + // TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs. // TODO: (Currently automatic type conversion only works for concrete types, via the Graphene preprocessor and not the full Graphene type system.) #[node_macro::node(category("General"), path(graphene_core::vector))] -async fn count_elements(_: impl Ctx, #[implementations(Table, Table, Table>, Table>, Table, Table)] source: Table) -> f64 { - source.len() as f64 +async fn count_elements( + _: impl Ctx, + #[implementations(Table, Table, Table>, Table>, Table, Table, Vec, Vec, Vec)] source: I, +) -> f64 { + source.count() as f64 } #[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))] From ab969ee021399aa87f7734f163e96e8652512475 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sun, 2 Nov 2025 09:39:42 +0100 Subject: [PATCH 5/6] Add Cache implementations for Vec --- node-graph/gcore/src/context_modification.rs | 1 + node-graph/interpreted-executor/src/node_registry.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/node-graph/gcore/src/context_modification.rs b/node-graph/gcore/src/context_modification.rs index f22ea1ec43..48bdf9f741 100644 --- a/node-graph/gcore/src/context_modification.rs +++ b/node-graph/gcore/src/context_modification.rs @@ -31,6 +31,7 @@ async fn context_modification( Context -> Vec, Context -> Vec, Context -> Vec, + Context -> Vec, Context -> Table, Context -> Table, Context -> Table>, diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 5a285f5298..23390a8b10 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -165,6 +165,7 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => Vec]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), #[cfg(feature = "gpu")] async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]), From 3e8449441f1082fa3b176c9efbaeb2bfbe90d8d5 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 3 Nov 2025 12:16:42 -0800 Subject: [PATCH 6/6] Code review --- node-graph/gcore/src/logic.rs | 12 ++++++++---- node-graph/gcore/src/vector/vector_nodes.rs | 13 ++++++++++++- .../interpreted-executor/src/node_registry.rs | 1 + 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 04442b99e5..a7c434a072 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -69,11 +69,15 @@ fn string_length(_: impl Ctx, string: String) -> f64 { string.chars().count() as f64 } -// Spliet a string by a specified delimeter ("1;2;3" e.t.c.) and return a vector of strings #[node_macro::node(category("Text"))] -fn split(_: impl Ctx, string: String, #[default("\\n")] delimeter: String) -> Vec { - let delimeter = delimeter.replace("\\n", "\n"); - string.split(&delimeter).map(|x| x.to_string()).collect() +fn string_split(_: impl Ctx, string: String, #[default("\\n")] delimeter: String, #[default(true)] delimeter_escaping: bool) -> Vec { + let delimeter = if delimeter_escaping { + delimeter.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\0", "\0").replace("\\\\", "\\") + } else { + delimeter + }; + + string.split(&delimeter).map(str::to_string).collect() } /// Evaluates either the "If True" or "If False" input branch based on whether the input condition is true or false. diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index e84db01a9c..6217778ee6 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -1973,7 +1973,18 @@ impl Count for Vec { #[node_macro::node(category("General"), path(graphene_core::vector))] async fn count_elements( _: impl Ctx, - #[implementations(Table, Table, Table>, Table>, Table, Table, Vec, Vec, Vec)] source: I, + #[implementations( + Table, + Table, + Table>, + Table>, + Table, + Table, + Vec, + Vec, + Vec, + )] + source: I, ) -> f64 { source.count() as f64 } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 23390a8b10..ab5fde8110 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -108,6 +108,7 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => [f64; 4]]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),