Geometry Nodes: String manipulation nodes

This patch adds four new nodes to a new "Text" category:
 - String Length: Outputs length of a string
 - String Substring: Outputs part of a string
 - Value to String: Converts a value to a string
 - String Join: Concatenates multiple strings with a delimiter

The initial use case of these nodes is the upcoming string to curve
node. However, they could also be used to calculate dynamic attribute
names, or with string attributes in the future.

Differential Revision: https://developer.blender.org/D12532
This commit is contained in:
Erik Abrahamsson 2021-09-21 14:11:32 -05:00 committed by Hans Goudey
parent 05ce5276db
commit 29e3545194
11 changed files with 234 additions and 1 deletions

View File

@ -598,6 +598,12 @@ geometry_node_categories = [
NodeItem("GeometryNodeLegacyRotatePoints", poll=geometry_nodes_fields_legacy_poll),
NodeItem("GeometryNodeLegacyAlignRotationToVector", poll=geometry_nodes_fields_legacy_poll),
]),
GeometryNodeCategory("GEO_TEXT", "Text", items=[
NodeItem("FunctionNodeStringLength"),
NodeItem("FunctionNodeStringSubstring"),
NodeItem("FunctionNodeValueToString"),
NodeItem("GeometryNodeStringJoin"),
]),
GeometryNodeCategory("GEO_UTILITIES", "Utilities", items=[
NodeItem("ShaderNodeMapRange"),
NodeItem("ShaderNodeClamp"),

View File

@ -1497,6 +1497,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_ATTRIBUTE_STATISTIC 1084
#define GEO_NODE_CURVE_SAMPLE 1085
#define GEO_NODE_INPUT_TANGENT 1086
#define GEO_NODE_STRING_JOIN 1087
/** \} */
@ -1510,6 +1511,9 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define FN_NODE_INPUT_VECTOR 1207
#define FN_NODE_INPUT_STRING 1208
#define FN_NODE_FLOAT_TO_INT 1209
#define FN_NODE_VALUE_TO_STRING 1210
#define FN_NODE_STRING_LENGTH 1211
#define FN_NODE_STRING_SUBSTRING 1212
/** \} */

View File

@ -5235,6 +5235,7 @@ static void registerGeometryNodes()
register_node_type_geo_realize_instances();
register_node_type_geo_sample_texture();
register_node_type_geo_select_by_handle_type();
register_node_type_geo_string_join();
register_node_type_geo_material_selection();
register_node_type_geo_separate_components();
register_node_type_geo_set_position();
@ -5254,6 +5255,9 @@ static void registerFunctionNodes()
register_node_type_fn_input_string();
register_node_type_fn_input_vector();
register_node_type_fn_random_float();
register_node_type_fn_string_length();
register_node_type_fn_string_substring();
register_node_type_fn_value_to_string();
}
void BKE_node_system_init(void)

View File

@ -139,6 +139,9 @@ set(SRC
function/nodes/node_fn_input_string.cc
function/nodes/node_fn_input_vector.cc
function/nodes/node_fn_random_float.cc
function/nodes/node_fn_string_length.cc
function/nodes/node_fn_string_substring.cc
function/nodes/node_fn_value_to_string.cc
function/node_function_util.cc
geometry/nodes/legacy/node_geo_material_assign.cc
@ -224,6 +227,7 @@ set(SRC
geometry/nodes/node_geo_realize_instances.cc
geometry/nodes/node_geo_separate_components.cc
geometry/nodes/node_geo_set_position.cc
geometry/nodes/node_geo_string_join.cc
geometry/nodes/node_geo_subdivision_surface.cc
geometry/nodes/node_geo_switch.cc
geometry/nodes/node_geo_transform.cc

View File

@ -26,6 +26,9 @@ void register_node_type_fn_float_to_int(void);
void register_node_type_fn_input_string(void);
void register_node_type_fn_input_vector(void);
void register_node_type_fn_random_float(void);
void register_node_type_fn_string_length(void);
void register_node_type_fn_string_substring(void);
void register_node_type_fn_value_to_string(void);
#ifdef __cplusplus
}

View File

@ -111,6 +111,7 @@ void register_node_type_geo_sample_texture(void);
void register_node_type_geo_select_by_handle_type(void);
void register_node_type_geo_separate_components(void);
void register_node_type_geo_set_position(void);
void register_node_type_geo_string_join(void);
void register_node_type_geo_subdivision_surface(void);
void register_node_type_geo_switch(void);
void register_node_type_geo_transform(void);

View File

@ -267,7 +267,10 @@ DefNode(FunctionNode, FN_NODE_FLOAT_COMPARE, def_float_compare, "FLOAT_COMPARE",
DefNode(FunctionNode, FN_NODE_FLOAT_TO_INT, def_float_to_int, "FLOAT_TO_INT", FloatToInt, "Float to Integer", "")
DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_STRING", InputString, "String", "")
DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "")
DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "")
DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "")
DefNode(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToString, "Value to String", "")
DefNode(FunctionNode, FN_NODE_STRING_LENGTH, 0, "STRING_LENGTH", StringLength, "String Length", "")
DefNode(FunctionNode, FN_NODE_STRING_SUBSTRING, 0, "STRING_SUBSTRING", StringSubstring, "String Substring", "")
DefNode(GeometryNode, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "LEGACY_ATTRIBUTE_CLAMP", LegacyAttributeClamp, "Attribute Clamp", "")
DefNode(GeometryNode, GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "LEGACY_ALIGN_ROTATION_TO_VECTOR", LegacyAlignRotationToVector, "Align Rotation to Vector", "")
@ -351,6 +354,7 @@ DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO",
DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "")
DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "")
DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "")
DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "String Join", "")
DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "")
DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "")
DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "")

View File

@ -0,0 +1,49 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "BLI_string_utf8.h"
#include <iomanip>
#include "node_function_util.hh"
namespace blender::nodes {
static void fn_node_string_length_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::String>("String");
b.add_output<decl::Int>("Length");
};
} // namespace blender::nodes
static void fn_node_string_length_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
static blender::fn::CustomMF_SI_SO<std::string, int> str_len_fn{
"String Length", [](const std::string &a) { return BLI_strlen_utf8(a.c_str()); }};
builder.set_matching_fn(&str_len_fn);
}
void register_node_type_fn_string_length()
{
static bNodeType ntype;
fn_node_type_base(&ntype, FN_NODE_STRING_LENGTH, "String Length", NODE_CLASS_CONVERTER, 0);
ntype.declare = blender::nodes::fn_node_string_length_declare;
ntype.build_multi_function = fn_node_string_length_build_multi_function;
nodeRegisterType(&ntype);
}

View File

@ -0,0 +1,54 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "BLI_string_utf8.h"
#include "node_function_util.hh"
namespace blender::nodes {
static void fn_node_string_substring_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::String>("String");
b.add_input<decl::Int>("Position");
b.add_input<decl::Int>("Length").min(0);
b.add_output<decl::String>("String");
};
} // namespace blender::nodes
static void fn_node_string_substring_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
static blender::fn::CustomMF_SI_SI_SI_SO<std::string, int, int, std::string> substring_fn{
"Substring", [](const std::string &str, int a, int b) {
const int len = BLI_strlen_utf8(str.c_str());
const int start = BLI_str_utf8_offset_from_index(str.c_str(), std::clamp(a, 0, len));
const int end = BLI_str_utf8_offset_from_index(str.c_str(), std::clamp(a + b, 0, len));
return str.substr(start, std::max<int>(end - start, 0));
}};
builder.set_matching_fn(&substring_fn);
}
void register_node_type_fn_string_substring()
{
static bNodeType ntype;
fn_node_type_base(&ntype, FN_NODE_STRING_SUBSTRING, "String Substring", NODE_CLASS_CONVERTER, 0);
ntype.declare = blender::nodes::fn_node_string_substring_declare;
ntype.build_multi_function = fn_node_string_substring_build_multi_function;
nodeRegisterType(&ntype);
}

View File

@ -0,0 +1,51 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "node_function_util.hh"
#include <iomanip>
namespace blender::nodes {
static void fn_node_value_to_string_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>("Value");
b.add_input<decl::Int>("Decimals").min(0);
b.add_output<decl::String>("String");
};
} // namespace blender::nodes
static void fn_node_value_to_string_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
static blender::fn::CustomMF_SI_SI_SO<float, int, std::string> to_str_fn{
"Value To String", [](float a, int b) {
std::stringstream stream;
stream << std::fixed << std::setprecision(std::max(0, b)) << a;
return stream.str();
}};
builder.set_matching_fn(&to_str_fn);
}
void register_node_type_fn_value_to_string()
{
static bNodeType ntype;
fn_node_type_base(&ntype, FN_NODE_VALUE_TO_STRING, "Value to String", NODE_CLASS_CONVERTER, 0);
ntype.declare = blender::nodes::fn_node_value_to_string_declare;
ntype.build_multi_function = fn_node_value_to_string_build_multi_function;
nodeRegisterType(&ntype);
}

View File

@ -0,0 +1,53 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "node_geometry_util.hh"
namespace blender::nodes {
static void geo_node_string_join_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::String>("Delimiter");
b.add_input<decl::String>("Strings").multi_input().hide_value();
b.add_output<decl::String>("String");
};
static void geo_node_string_join_exec(GeoNodeExecParams params)
{
Vector<std::string> strings = params.extract_multi_input<std::string>("Strings");
const std::string delim = params.extract_input<std::string>("Delimiter");
std::string output;
for (const int i : strings.index_range()) {
output += strings[i];
if (i < (strings.size() - 1)) {
output += delim;
}
}
params.set_output("String", std::move(output));
}
} // namespace blender::nodes
void register_node_type_geo_string_join()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_STRING_JOIN, "String Join", NODE_CLASS_CONVERTER, 0);
ntype.geometry_node_execute = blender::nodes::geo_node_string_join_exec;
ntype.declare = blender::nodes::geo_node_string_join_declare;
nodeRegisterType(&ntype);
}