Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
# SPDX-FileCopyrightText: 2021 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
..
include
../intern
../shader
../../editors/include
../../makesrna
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
nodes/node_fn_align_euler_to_vector.cc
nodes/node_fn_align_rotation_to_vector.cc
nodes/node_fn_axes_to_rotation.cc
nodes/node_fn_axis_angle_to_rotation.cc
nodes/node_fn_bit_math.cc
nodes/node_fn_boolean_math.cc
nodes/node_fn_combine_color.cc
nodes/node_fn_combine_matrix.cc
nodes/node_fn_combine_transform.cc
nodes/node_fn_compare.cc
nodes/node_fn_euler_to_rotation.cc
nodes/node_fn_find_in_string.cc
nodes/node_fn_float_to_int.cc
nodes/node_fn_format_string.cc
nodes/node_fn_hash_value.cc
nodes/node_fn_input_bool.cc
nodes/node_fn_input_color.cc
nodes/node_fn_input_int.cc
nodes/node_fn_input_int_vector.cc
nodes/node_fn_input_menu.cc
nodes/node_fn_input_rotation.cc
nodes/node_fn_input_special_characters.cc
nodes/node_fn_input_string.cc
nodes/node_fn_input_vector.cc
nodes/node_fn_integer_math.cc
nodes/node_fn_invert_matrix.cc
nodes/node_fn_invert_rotation.cc
nodes/node_fn_match_string.cc
nodes/node_fn_matrix_determinant.cc
nodes/node_fn_matrix_multiply.cc
nodes/node_fn_matrix_svd.cc
nodes/node_fn_project_point.cc
nodes/node_fn_quaternion_to_rotation.cc
nodes/node_fn_random_value.cc
nodes/node_fn_replace_string.cc
nodes/node_fn_reverse_string.cc
nodes/node_fn_rotate_euler.cc
nodes/node_fn_rotate_rotation.cc
nodes/node_fn_rotate_vector.cc
nodes/node_fn_rotation_to_axis_angle.cc
nodes/node_fn_rotation_to_euler.cc
nodes/node_fn_rotation_to_quaternion.cc
nodes/node_fn_separate_color.cc
nodes/node_fn_separate_matrix.cc
nodes/node_fn_separate_transform.cc
nodes/node_fn_set_string_case.cc
nodes/node_fn_slice_string.cc
nodes/node_fn_split_string.cc
nodes/node_fn_string_length.cc
nodes/node_fn_string_to_value.cc
nodes/node_fn_transform_direction.cc
nodes/node_fn_transform_point.cc
nodes/node_fn_transpose_matrix.cc
nodes/node_fn_trim_string.cc
nodes/node_fn_value_to_string.cc
node_function_util.cc
include/NOD_fn_format_string.hh
node_function_util.hh
)
set(LIB
PRIVATE bf::blenfont
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blentranslation
PRIVATE bf::blenloader
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::functions
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::intern::guardedalloc
PRIVATE bf::windowmanager
PRIVATE bf::extern::fast_float
)
add_node_discovery(
bf_nodes_functions_generated
"${SRC}"
${CMAKE_CURRENT_BINARY_DIR}/register_function_nodes.cc
register_function_nodes
)
list(APPEND LIB
bf_nodes_functions_generated
)
blender_add_lib(bf_nodes_function "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
target_link_libraries(bf_nodes_functions_generated bf_nodes_function)
blender_set_target_unity_build(bf_nodes_function 10)

View File

@@ -0,0 +1,95 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DNA_node_types.h"
#include "NOD_socket_items.hh"
namespace blender::nodes {
/**
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) for format
* string items.
*/
struct FormatStringItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeFunctionFormatStringItem;
static StructRNA **item_srna;
static constexpr StringRefNull node_idname = "FunctionNodeFormatString";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_name_validation = true;
static constexpr bool has_custom_initial_name = true;
static constexpr char unique_name_separator = '_';
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_format_string_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_format_string_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_format_string_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_format_string_items";
};
struct rna_names {
static constexpr StringRefNull items = "format_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<NodeFunctionFormatStringItem> get_items_from_node(
bNode &node)
{
auto *storage = static_cast<NodeFunctionFormatString *>(node.storage);
return {&storage->items, &storage->items_num, &storage->active_index};
}
static void copy_item(const NodeFunctionFormatStringItem &src, NodeFunctionFormatStringItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(NodeFunctionFormatStringItem *item)
{
MEM_SAFE_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const NodeFunctionFormatStringItem &item)
{
return item.socket_type;
}
static char **get_name(NodeFunctionFormatStringItem &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/)
{
return ELEM(socket_type, SOCK_INT, SOCK_FLOAT, SOCK_STRING);
}
static void init_with_socket_type_and_name(bNode &node,
NodeFunctionFormatStringItem &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeFunctionFormatString *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<FormatStringItemsAccessor>(node, item, name);
}
static std::string custom_initial_name(const bNode &node, StringRef src_name);
static std::string validate_name(const StringRef name);
static std::string socket_identifier_for_item(const NodeFunctionFormatStringItem &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <optional>
#include "BLI_string.h"
#include "node_function_util.hh"
#include "node_util.hh"
#include "NOD_socket_search_link.hh"
namespace blender {
static bool fn_node_poll_default(const bke::bNodeType * /*ntype*/,
const bNodeTree *ntree,
const char **r_disabled_hint)
{
/* Function nodes are only supported in simulation node trees so far. */
if (!STREQ(ntree->idname, "GeometryNodeTree")) {
*r_disabled_hint = RPT_("Not a geometry node tree");
return false;
}
return true;
}
void fn_node_type_base(bke::bNodeType *ntype,
UString idname,
const std::optional<int16_t> legacy_type)
{
bke::node_type_base(*ntype, idname, legacy_type);
ntype->poll = fn_node_poll_default;
ntype->insert_link = node_insert_link_default;
ntype->gather_link_search_ops = nodes::search_link_ops_for_basic_node;
}
static bool fn_cmp_node_poll_default(const bke::bNodeType * /*ntype*/,
const bNodeTree *ntree,
const char **r_disabled_hint)
{
if (!STR_ELEM(ntree->idname, "GeometryNodeTree", "CompositorNodeTree")) {
*r_disabled_hint = RPT_("Not a geometry or compositor node tree");
return false;
}
return true;
}
void fn_cmp_node_type_base(bke::bNodeType *ntype,
UString idname,
const std::optional<int16_t> legacy_type)
{
bke::node_type_base(*ntype, idname, legacy_type);
ntype->poll = fn_cmp_node_poll_default;
ntype->insert_link = node_insert_link_default;
ntype->gather_link_search_ops = nodes::search_link_ops_for_basic_node;
}
} // namespace blender

View File

@@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstring>
#include <optional>
#include "BLI_math_vector.hh" // IWYU pragma: export
#include "DNA_node_types.h"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh" // IWYU pragma: export
#include "NOD_multi_function.hh" // IWYU pragma: export
#include "NOD_register.hh" // IWYU pragma: export
#include "NOD_socket_declarations.hh" // IWYU pragma: export
#include "node_util.hh" // IWYU pragma: export
#include "FN_multi_function_builder.hh" // IWYU pragma: export
#include "RNA_access.hh" // IWYU pragma: export
namespace blender {
void fn_node_type_base(bke::bNodeType *ntype,
UString idname,
std::optional<int16_t> legacy_type = std::nullopt);
void fn_cmp_node_type_base(bke::bNodeType *ntype,
UString idname,
std::optional<int16_t> legacy_type = std::nullopt);
} // namespace blender

View File

@@ -0,0 +1,281 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_rna_define.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_align_euler_to_vector_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Rotation"_ustr).subtype(PROP_EULER).hide_value();
b.add_input<decl::Float>("Factor"_ustr)
.default_value(1.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR);
b.add_input<decl::Vector>("Vector"_ustr).default_value({0.0, 0.0, 1.0});
b.add_output<decl::Vector>("Rotation"_ustr).subtype(PROP_EULER);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "axis", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
layout.prop(ptr, "pivot_axis", UI_ITEM_NONE, IFACE_("Pivot"), ICON_NONE);
}
static void align_rotations_auto_pivot(const IndexMask &mask,
const VArray<float3> &input_rotations,
const VArray<float3> &vectors,
const VArray<float> &factors,
const float3 local_main_axis,
MutableSpan<float3> output_rotations)
{
mask.foreach_index([&](const int64_t i) {
const float3 vector = vectors[i];
if (math::is_zero(vector)) {
output_rotations[i] = input_rotations[i];
return;
}
float old_rotation[3][3];
eul_to_mat3(old_rotation, input_rotations[i]);
float3 old_axis;
mul_v3_m3v3(old_axis, old_rotation, local_main_axis);
const float3 new_axis = math::normalize(vector);
float3 rotation_axis = math::cross_high_precision(old_axis, new_axis);
if (math::is_zero(rotation_axis)) {
/* The vectors are linearly dependent, so we fall back to another axis. */
rotation_axis = math::cross_high_precision(old_axis, float3(1, 0, 0));
if (math::is_zero(rotation_axis)) {
/* This is now guaranteed to not be zero. */
rotation_axis = math::cross_high_precision(old_axis, float3(0, 1, 0));
}
}
const float full_angle = angle_normalized_v3v3(old_axis, new_axis);
const float angle = factors[i] * full_angle;
float rotation[3][3];
axis_angle_to_mat3(rotation, rotation_axis, angle);
float new_rotation_matrix[3][3];
mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation);
float3 new_rotation;
mat3_to_eul(new_rotation, new_rotation_matrix);
output_rotations[i] = new_rotation;
});
}
static void align_rotations_fixed_pivot(const IndexMask &mask,
const VArray<float3> &input_rotations,
const VArray<float3> &vectors,
const VArray<float> &factors,
const float3 local_main_axis,
const float3 local_pivot_axis,
MutableSpan<float3> output_rotations)
{
mask.foreach_index([&](const int64_t i) {
if (local_main_axis == local_pivot_axis) {
/* Can't compute any meaningful rotation angle in this case. */
output_rotations[i] = input_rotations[i];
return;
}
const float3 vector = vectors[i];
if (math::is_zero(vector)) {
output_rotations[i] = input_rotations[i];
return;
}
float old_rotation[3][3];
eul_to_mat3(old_rotation, input_rotations[i]);
float3 old_axis;
mul_v3_m3v3(old_axis, old_rotation, local_main_axis);
float3 pivot_axis;
mul_v3_m3v3(pivot_axis, old_rotation, local_pivot_axis);
float full_angle = angle_signed_on_axis_v3v3_v3(vector, old_axis, pivot_axis);
if (full_angle > M_PI) {
/* Make sure the point is rotated as little as possible. */
full_angle -= 2.0f * M_PI;
}
const float angle = factors[i] * full_angle;
float rotation[3][3];
axis_angle_to_mat3(rotation, pivot_axis, angle);
float new_rotation_matrix[3][3];
mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation);
float3 new_rotation;
mat3_to_eul(new_rotation, new_rotation_matrix);
output_rotations[i] = new_rotation;
});
}
class MF_AlignEulerToVector : public mf::MultiFunction {
private:
int main_axis_mode_;
int pivot_axis_mode_;
public:
MF_AlignEulerToVector(int main_axis_mode, int pivot_axis_mode)
: main_axis_mode_(main_axis_mode), pivot_axis_mode_(pivot_axis_mode)
{
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Align Euler to Vector", signature};
builder.single_input<float3>("Rotation");
builder.single_input<float>("Factor");
builder.single_input<float3>("Vector");
builder.single_output<float3>("Rotation");
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArray<float3> &input_rotations = params.readonly_single_input<float3>(0, "Rotation");
const VArray<float> &factors = params.readonly_single_input<float>(1, "Factor");
const VArray<float3> &vectors = params.readonly_single_input<float3>(2, "Vector");
auto output_rotations = params.uninitialized_single_output<float3>(3, "Rotation");
float3 local_main_axis = {0.0f, 0.0f, 0.0f};
local_main_axis[main_axis_mode_] = 1;
if (pivot_axis_mode_ == FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_AUTO) {
align_rotations_auto_pivot(
mask, input_rotations, vectors, factors, local_main_axis, output_rotations);
}
else {
float3 local_pivot_axis = {0.0f, 0.0f, 0.0f};
local_pivot_axis[pivot_axis_mode_ - 1] = 1;
align_rotations_fixed_pivot(mask,
input_rotations,
vectors,
factors,
local_main_axis,
local_pivot_axis,
output_rotations);
}
}
ExecutionHints get_execution_hints() const override
{
ExecutionHints hints;
hints.min_grain_size = 512;
return hints;
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const bNode &node = builder.node();
builder.construct_and_set_matching_fn<MF_AlignEulerToVector>(node.custom1, node.custom2);
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem axis_items[] = {
{FN_NODE_ALIGN_EULER_TO_VECTOR_AXIS_X,
"X",
ICON_NONE,
"X",
"Align the X axis with the vector"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_AXIS_Y,
"Y",
ICON_NONE,
"Y",
"Align the Y axis with the vector"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_AXIS_Z,
"Z",
ICON_NONE,
"Z",
"Align the Z axis with the vector"},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem pivot_axis_items[] = {
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_AUTO,
"AUTO",
ICON_NONE,
"Auto",
"Automatically detect the best rotation axis to rotate towards the vector"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_X,
"X",
ICON_NONE,
"X",
"Rotate around the local X axis"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_Y,
"Y",
ICON_NONE,
"Y",
"Rotate around the local Y axis"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_Z,
"Z",
ICON_NONE,
"Z",
"Rotate around the local Z axis"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"axis",
"Axis",
"Axis to align to the vector",
axis_items,
NOD_inline_enum_accessors(custom1),
std::nullopt,
nullptr,
true);
RNA_def_node_enum(srna,
"pivot_axis",
"Pivot Axis",
"Axis to rotate around",
pivot_axis_items,
NOD_inline_enum_accessors(custom2),
std::nullopt,
nullptr,
true);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeAlignEulerToVector"_ustr, FN_NODE_ALIGN_EULER_TO_VECTOR);
ntype.ui_name = "Align Euler to Vector";
ntype.ui_description = "Orient an Euler rotation along the given direction";
ntype.enum_name_legacy = "ALIGN_EULER_TO_VECTOR";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.build_multi_function = node_build_multi_function;
ntype.deprecation_notice = N_("Use the \"Align Rotation to Vector\" node instead");
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_align_euler_to_vector_cc

View File

@@ -0,0 +1,293 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_quaternion.hh"
#include "BLI_math_rotation.hh"
#include "BLI_math_vector.h"
#include "BLI_math_vector.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_rna_define.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_align_rotation_to_vector_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
b.is_function_node();
b.add_input<decl::Rotation>("Rotation"_ustr).hide_value();
b.add_output<decl::Rotation>("Rotation"_ustr).align_with_previous();
b.add_input<decl::Float>("Factor"_ustr)
.default_value(1.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR);
b.add_input<decl::Vector>("Vector"_ustr).default_value({0.0, 0.0, 1.0}).subtype(PROP_XYZ);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "axis", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
layout.prop(ptr, "pivot_axis", UI_ITEM_NONE, IFACE_("Pivot"), ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = int16_t(math::Axis::Z);
}
static void align_rotations_auto_pivot(const IndexMask &mask,
const VArray<math::Quaternion> &input_rotations,
const VArray<float3> &vectors,
const VArray<float> &factors,
const float3 local_main_axis,
MutableSpan<math::Quaternion> output_rotations)
{
mask.foreach_index([&](const int64_t i) {
const math::Quaternion old_rotation = input_rotations[i];
const float3 vector = vectors[i];
if (math::is_zero(vector)) {
output_rotations[i] = old_rotation;
return;
}
const float3 old_axis = math::transform_point(old_rotation, local_main_axis);
const float3 new_axis = math::normalize(vector);
float3 rotation_axis = math::cross_high_precision(old_axis, new_axis);
if (math::is_zero(rotation_axis)) {
/* The vectors are linearly dependent, so we fall back to another axis. */
rotation_axis = math::cross_high_precision(old_axis, float3(1, 0, 0));
if (math::is_zero(rotation_axis)) {
/* This is now guaranteed to not be zero. */
rotation_axis = math::cross_high_precision(old_axis, float3(0, 1, 0));
}
}
const float full_angle = angle_normalized_v3v3(old_axis, new_axis);
const float angle = factors[i] * full_angle;
const math::AxisAngle axis_angle = math::AxisAngle(math::normalize(rotation_axis), angle);
output_rotations[i] = math::to_quaternion(axis_angle) * old_rotation;
});
}
static void align_rotations_fixed_pivot(const IndexMask &mask,
const VArray<math::Quaternion> &input_rotations,
const VArray<float3> &vectors,
const VArray<float> &factors,
const float3 local_main_axis,
const float3 local_pivot_axis,
MutableSpan<math::Quaternion> output_rotations)
{
mask.foreach_index([&](const int64_t i) {
const math::Quaternion old_rotation = input_rotations[i];
if (local_main_axis == local_pivot_axis) {
/* Can't compute any meaningful rotation angle in this case. */
output_rotations[i] = old_rotation;
return;
}
const float3 vector = vectors[i];
if (math::is_zero(vector)) {
output_rotations[i] = old_rotation;
return;
}
const float3 old_axis = math::transform_point(old_rotation, local_main_axis);
const float3 pivot_axis = math::transform_point(old_rotation, local_pivot_axis);
float full_angle = angle_signed_on_axis_v3v3_v3(vector, old_axis, pivot_axis);
if (full_angle > M_PI) {
/* Make sure the point is rotated as little as possible. */
full_angle -= 2.0f * M_PI;
}
const float angle = factors[i] * full_angle;
const math::AxisAngle axis_angle = math::AxisAngle(math::normalize(pivot_axis), angle);
output_rotations[i] = math::to_quaternion(axis_angle) * old_rotation;
});
}
class AlignRotationToVectorFunction : public mf::MultiFunction {
math::Axis main_axis_mode_;
NodeAlignEulerToVectorPivotAxis pivot_axis_mode_;
public:
AlignRotationToVectorFunction(const math::Axis main_axis_mode,
const NodeAlignEulerToVectorPivotAxis pivot_axis_mode)
: main_axis_mode_(main_axis_mode), pivot_axis_mode_(pivot_axis_mode)
{
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Align Rotation to Vector", signature};
builder.single_input<math::Quaternion>("Rotation");
builder.single_input<float>("Factor");
builder.single_input<float3>("Vector");
builder.single_output<math::Quaternion>("Rotation");
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const final
{
const VArray input_rotations = params.readonly_single_input<math::Quaternion>(0, "Rotation");
const VArray<float> factors = params.readonly_single_input<float>(1, "Factor");
const VArray<float3> vectors = params.readonly_single_input<float3>(2, "Vector");
MutableSpan output_rotations = params.uninitialized_single_output<math::Quaternion>(
3, "Rotation");
float3 local_main_axis = {0.0f, 0.0f, 0.0f};
local_main_axis[main_axis_mode_.as_int()] = 1.0f;
if (pivot_axis_mode_ == FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_AUTO) {
align_rotations_auto_pivot(
mask, input_rotations, vectors, factors, local_main_axis, output_rotations);
}
else {
float3 local_pivot_axis = {0.0f, 0.0f, 0.0f};
local_pivot_axis[pivot_axis_mode_ - 1] = 1;
align_rotations_fixed_pivot(mask,
input_rotations,
vectors,
factors,
local_main_axis,
local_pivot_axis,
output_rotations);
}
}
ExecutionHints get_execution_hints() const final
{
ExecutionHints hints;
hints.min_grain_size = 512;
return hints;
}
void hash_unique(UniqueHashBytes &hash) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(main_axis_mode_);
hash.add(pivot_axis_mode_);
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const bNode &node = builder.node();
builder.construct_and_set_matching_fn<AlignRotationToVectorFunction>(
math::Axis::from_int(node.custom1), NodeAlignEulerToVectorPivotAxis(node.custom2));
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
const math::Axis main_axis_mode = math::Axis::from_int(node->custom1);
const NodeAlignEulerToVectorPivotAxis pivot_axis_mode = NodeAlignEulerToVectorPivotAxis(
node->custom2);
float3 local_main_axis = {0.0f, 0.0f, 0.0f};
local_main_axis[main_axis_mode.as_int()] = 1.0f;
if (pivot_axis_mode == FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_AUTO) {
return GPU_stack_link(
mat, node, "align_rotation_to_vector_auto_pivot", in, out, GPU_constant(local_main_axis));
}
float3 local_pivot_axis = {0.0f, 0.0f, 0.0f};
local_pivot_axis[pivot_axis_mode - 1] = 1.0f;
return GPU_stack_link(mat,
node,
"align_rotation_to_vector_fixed_pivot",
in,
out,
GPU_constant(local_main_axis),
GPU_constant(local_pivot_axis));
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem axis_items[] = {
{int(math::Axis::X), "X", ICON_NONE, "X", "Align the X axis with the vector"},
{int(math::Axis::Y), "Y", ICON_NONE, "Y", "Align the Y axis with the vector"},
{int(math::Axis::Z), "Z", ICON_NONE, "Z", "Align the Z axis with the vector"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"axis",
"Axis",
"Axis to align to the vector",
axis_items,
NOD_inline_enum_accessors(custom1));
static const EnumPropertyItem pivot_axis_items[] = {
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_AUTO,
"AUTO",
ICON_NONE,
"Auto",
"Automatically detect the best rotation axis to rotate towards the vector"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_X,
"X",
ICON_NONE,
"X",
"Rotate around the local X axis"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_Y,
"Y",
ICON_NONE,
"Y",
"Rotate around the local Y axis"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_Z,
"Z",
ICON_NONE,
"Z",
"Rotate around the local Z axis"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"pivot_axis",
"Pivot Axis",
"Axis to rotate around",
pivot_axis_items,
NOD_inline_enum_accessors(custom2));
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(
&ntype, "FunctionNodeAlignRotationToVector"_ustr, FN_NODE_ALIGN_ROTATION_TO_VECTOR);
ntype.ui_name = "Align Rotation to Vector";
ntype.ui_description = "Orient a rotation along the given direction";
ntype.enum_name_legacy = "ALIGN_ROTATION_TO_VECTOR";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.draw_buttons = node_layout;
ntype.build_multi_function = node_build_multi_function;
ntype.gpu_fn = node_gpu_material;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_align_rotation_to_vector_cc

View File

@@ -0,0 +1,241 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_node_extra_info.hh"
#include "NOD_rna_define.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_axes_to_rotation_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Primary Axis"_ustr).default_value(float3(0, 0, 1));
b.add_input<decl::Vector>("Secondary Axis"_ustr).default_value(float3(1, 0, 0));
b.add_output<decl::Rotation>("Rotation"_ustr);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = int(math::Axis::Z);
node->custom2 = int(math::Axis::X);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "primary_axis", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
layout.prop(ptr, "secondary_axis", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
static float3 get_orthogonal_of_non_zero_vector(const float3 &v)
{
BLI_assert(!math::is_zero(v));
if (v.x != -v.y) {
return float3{-v.y, v.x, 0.0f};
}
if (v.x != -v.z) {
return float3(-v.z, 0.0f, v.x);
}
return {0.0f, -v.z, v.y};
}
class AxesToRotationFunction : public mf::MultiFunction {
private:
math::Axis primary_axis_;
math::Axis secondary_axis_;
math::Axis tertiary_axis_;
public:
AxesToRotationFunction(const math::Axis primary_axis, const math::Axis secondary_axis)
: primary_axis_(primary_axis), secondary_axis_(secondary_axis)
{
BLI_assert(primary_axis_ != secondary_axis_);
/* Through cancellation this will set the last axis to be the one that's neither the primary
* nor secondary axis. */
tertiary_axis_ = math::Axis::from_int((0 + 1 + 2) - primary_axis.as_int() -
secondary_axis.as_int());
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Axes to Rotation", signature};
builder.single_input<float3>("Primary");
builder.single_input<float3>("Secondary");
builder.single_output<math::Quaternion>("Rotation");
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArray<float3> primaries = params.readonly_single_input<float3>(0, "Primary");
const VArray<float3> secondaries = params.readonly_single_input<float3>(1, "Secondary");
MutableSpan r_rotations = params.uninitialized_single_output<math::Quaternion>(2, "Rotation");
/* Might have to invert the axis to make sure that the created matrix has determinant 1. */
const bool invert_tertiary = (secondary_axis_.as_int() + 1) % 3 == primary_axis_.as_int();
const float tertiary_factor = invert_tertiary ? -1.0f : 1.0f;
mask.foreach_index([&](const int64_t i) {
float3 primary = math::normalize(primaries[i]);
float3 secondary = secondaries[i];
float3 tertiary;
const bool primary_is_non_zero = !math::is_zero(primary);
const bool secondary_is_non_zero = !math::is_zero(secondary);
if (primary_is_non_zero && secondary_is_non_zero) {
tertiary = math::cross(primary, secondary);
if (math::is_zero(tertiary)) {
tertiary = get_orthogonal_of_non_zero_vector(primary);
}
tertiary = math::normalize(tertiary);
secondary = math::cross(tertiary, primary);
}
else if (primary_is_non_zero) {
secondary = get_orthogonal_of_non_zero_vector(primary);
secondary = math::normalize(secondary);
tertiary = math::cross(primary, secondary);
}
else if (secondary_is_non_zero) {
secondary = math::normalize(secondary);
primary = get_orthogonal_of_non_zero_vector(secondary);
primary = math::normalize(primary);
tertiary = math::cross(primary, secondary);
}
else {
r_rotations[i] = math::Quaternion::identity();
return;
}
float3x3 mat;
mat[primary_axis_.as_int()] = primary;
mat[secondary_axis_.as_int()] = secondary;
mat[tertiary_axis_.as_int()] = tertiary_factor * tertiary;
BLI_assert(math::is_orthonormal(mat));
BLI_assert(std::abs(math::determinant(mat) - 1.0f) < 0.0001f);
r_rotations[i] = math::to_quaternion(mat);
});
};
void hash_unique(UniqueHashBytes &hash) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(primary_axis_);
hash.add(secondary_axis_);
hash.add(tertiary_axis_);
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const bNode &node = builder.node();
if (node.custom1 == node.custom2) {
static auto fallback_fn = mf::build::SI2_SO<float3, float3, math::Quaternion>(
"Axes to Rotation fallback",
[](const float3 & /*a*/, const float3 & /*b*/) { return math::Quaternion::identity(); });
builder.set_matching_fn(fallback_fn);
return;
}
builder.construct_and_set_matching_fn<AxesToRotationFunction>(
math::Axis::from_int(node.custom1), math::Axis::from_int(node.custom2));
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
if (node->custom1 == node->custom2) {
return GPU_stack_link(mat, node, "axes_to_rotation_identity", in, out);
}
const float primary = float(node->custom1);
const float secondary = float(node->custom2);
/* Through cancellation this will set the last axis to be the one that's neither the primary
* nor secondary axis. */
const int tertiary_axis = (0 + 1 + 2) - node->custom1 - node->custom2;
const float tertiary = float(tertiary_axis);
/* Might have to invert the axis to make sure that the created matrix has determinant 1. */
const bool invert_tertiary = (node->custom2 + 1) % 3 == node->custom1;
const float tertiary_factor = invert_tertiary ? -1.0f : 1.0f;
return GPU_stack_link(mat,
node,
"axes_to_rotation",
in,
out,
GPU_constant(&primary),
GPU_constant(&secondary),
GPU_constant(&tertiary),
GPU_constant(&tertiary_factor));
}
static void node_extra_info(NodeExtraInfoParams &params)
{
if (params.node.custom1 == params.node.custom2) {
NodeExtraInfoRow row;
row.text = RPT_("Equal Axes");
row.tooltip = TIP_("The primary and secondary axis have to be different");
row.icon = ICON_ERROR;
params.rows.append(std::move(row));
}
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem axis_items[] = {
{int(math::Axis::X), "X", ICON_NONE, "X", ""},
{int(math::Axis::Y), "Y", ICON_NONE, "Y", ""},
{int(math::Axis::Z), "Z", ICON_NONE, "Z", ""},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"primary_axis",
"Primary Axis",
"Axis that is aligned exactly to the provided primary direction",
axis_items,
NOD_inline_enum_accessors(custom1));
RNA_def_node_enum(
srna,
"secondary_axis",
"Secondary Axis",
"Axis that is aligned as well as possible given the alignment of the primary axis",
axis_items,
NOD_inline_enum_accessors(custom2));
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeAxesToRotation"_ustr, FN_NODE_AXES_TO_ROTATION);
ntype.ui_name = "Axes to Rotation";
ntype.ui_description =
"Create a rotation from a primary and (ideally orthogonal) secondary axis";
ntype.enum_name_legacy = "AXES_TO_ROTATION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.build_multi_function = node_build_multi_function;
ntype.draw_buttons = node_layout;
ntype.get_extra_info = node_extra_info;
ntype.gpu_fn = node_gpu_material;
node_rna(ntype.rna_ext.srna);
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_axes_to_rotation_cc

View File

@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_axis_angle.hh"
#include "NOD_inverse_eval_params.hh"
#include "NOD_value_elem_eval.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_axis_angle_to_rotation_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Axis"_ustr).default_value({0.0f, 0.0f, 1.0f});
b.add_input<decl::Float>("Angle"_ustr).subtype(PROP_ANGLE);
b.add_output<decl::Rotation>("Rotation"_ustr);
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI2_SO<float3, float, math::Quaternion>(
"Axis Angle to Quaternion", [](float3 axis, float angle) {
if (UNLIKELY(math::is_zero(axis))) {
return math::Quaternion::identity();
}
const float3 axis_normalized = math::normalize(axis);
const math::AxisAngle axis_angle = math::AxisAngle(axis_normalized, angle);
return math::to_quaternion(axis_angle);
});
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "axis_angle_to_rotation", in, out);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
RotationElem rotation_elem;
rotation_elem.axis = params.get_input_elem<VectorElem>("Axis"_ustr);
rotation_elem.angle = params.get_input_elem<FloatElem>("Angle"_ustr);
if (rotation_elem) {
rotation_elem.euler = VectorElem::all();
}
params.set_output_elem("Rotation"_ustr, rotation_elem);
}
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams &params)
{
using namespace value_elem;
const RotationElem rotation_elem = params.get_output_elem<RotationElem>("Rotation"_ustr);
VectorElem axis_elem = rotation_elem.axis;
FloatElem angle_elem = rotation_elem.angle;
params.set_input_elem("Axis"_ustr, axis_elem);
params.set_input_elem("Angle"_ustr, angle_elem);
}
static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
{
using namespace inverse_eval;
const math::Quaternion rotation = params.get_output<math::Quaternion>("Rotation"_ustr);
const math::AxisAngle axis_angle = math::to_axis_angle(rotation);
params.set_input("Axis"_ustr, axis_angle.axis());
params.set_input("Angle"_ustr, axis_angle.angle().radian());
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(
&ntype, "FunctionNodeAxisAngleToRotation"_ustr, FN_NODE_AXIS_ANGLE_TO_ROTATION);
ntype.ui_name = "Axis Angle to Rotation";
ntype.ui_description = "Build a rotation from an axis and a rotation around that axis";
ntype.enum_name_legacy = "AXIS_ANGLE_TO_ROTATION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;
ntype.eval_inverse = node_eval_inverse;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_axis_angle_to_rotation_cc

View File

@@ -0,0 +1,199 @@
/* SPDX-FileCopyrightText: 2025 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "RNA_enum_types.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "FN_multi_function_registry.hh"
#include "node_function_util.hh"
namespace blender {
static_assert(-1 == ~0, "Two's complement must be used for bitwise operations.");
namespace nodes::node_fn_bit_math_cc {
enum BitMathOperation : int16_t {
And = 0,
Or = 1,
Xor = 2,
Not = 3,
Shift = 4,
Rotate = 5,
};
const std::array<EnumPropertyItem, 7> bit_math_operation_items = {{
{BitMathOperation::And,
"AND",
0,
"And",
"Returns a value where the bits of A and B are both set"},
{BitMathOperation::Or,
"OR",
0,
"Or",
"Returns a value where the bits of either A or B are set"},
{BitMathOperation::Xor,
"XOR",
0,
"Exclusive Or",
"Returns a value where only one bit from A and B is set"},
{BitMathOperation::Not,
"NOT",
0,
"Not",
"Returns the opposite bit value of A, in decimal it is equivalent of A = -A - 1"},
{BitMathOperation::Shift,
"SHIFT",
0,
"Shift",
"Shifts the bit values of A by the specified Shift amount. Positive values shift left, "
"negative values shift right."},
{BitMathOperation::Rotate,
"ROTATE",
0,
"Rotate",
"Rotates the bit values of A by the specified Shift amount. Positive values rotate left, "
"negative values rotate right."},
{0, nullptr, 0, nullptr, nullptr},
}};
constexpr static int32_t max_shift = sizeof(int32_t) * CHAR_BIT - 1;
constexpr static int32_t min_shift = -max_shift;
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Int>("A"_ustr);
auto &b_socket = b.add_input<decl::Int>("B"_ustr);
auto &shift = b.add_input<decl::Int>("Shift"_ustr).min(min_shift).max(max_shift);
b.add_output<decl::Int>("Value"_ustr);
if (const bNode *node = b.node_or_null()) {
const BitMathOperation operation = BitMathOperation(node->custom1);
b_socket.available(!ELEM(
operation, BitMathOperation::Not, BitMathOperation::Shift, BitMathOperation::Rotate));
shift.available(ELEM(operation, BitMathOperation::Shift, BitMathOperation::Rotate));
}
};
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
}
class SocketSearchOp {
public:
UString socket_name;
BitMathOperation operation;
void operator()(LinkSearchOpParams &params)
{
bNode &node = params.add_node("FunctionNodeBitMath"_ustr);
node.custom1 = int16_t(operation);
params.update_and_connect_available_socket(node, socket_name);
}
};
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
if (!params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_INT)) {
return;
}
const bool is_integer = params.other_socket().type == SOCK_INT;
const int weight = is_integer ? 0 : -1;
const UString socket_name = (params.in_out() == SOCK_OUT) ? "Value"_ustr : "A"_ustr;
for (const auto &item : bit_math_operation_items) {
if (item.name != nullptr && item.identifier[0] != '\0') {
params.add_item(
IFACE_(item.name), SocketSearchOp{socket_name, BitMathOperation(item.value)}, weight);
}
}
}
static void node_label(const bNodeTree * /*ntree*/,
const bNode *node,
char *label,
int label_maxncpy)
{
const char *operation_name = IFACE_("Unknown");
/* NOTE: This assumes that the matching RNA enum property also uses the default i18n context, and
* needs to be kept manually in sync. */
RNA_enum_name_gettexted(
bit_math_operation_items.data(), node->custom1, BLT_I18NCONTEXT_DEFAULT, &operation_name);
BLI_snprintf_utf8(label, label_maxncpy, IFACE_("Bitwise %s"), operation_name);
}
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
{
const BitMathOperation operation = BitMathOperation(bnode.custom1);
switch (operation) {
case BitMathOperation::And:
return &fn::multi_function::registry::lookup("int & int"_ustr);
case BitMathOperation::Or:
return &fn::multi_function::registry::lookup("int | int"_ustr);
case BitMathOperation::Xor:
return &fn::multi_function::registry::lookup("int ^ int"_ustr);
case BitMathOperation::Not:
return &fn::multi_function::registry::lookup("~int"_ustr);
case BitMathOperation::Shift:
return &fn::multi_function::registry::lookup("shift(int, int)"_ustr);
case BitMathOperation::Rotate:
return &fn::multi_function::registry::lookup("rotate(int, int)"_ustr);
}
BLI_assert_unreachable();
return nullptr;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const mf::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
static void node_rna(StructRNA *srna)
{
PropertyRNA *prop = RNA_def_node_enum(srna,
"operation",
"Operation",
"",
bit_math_operation_items.data(),
NOD_inline_enum_accessors(custom1),
BitMathOperation::And);
RNA_def_property_update_runtime(prop, rna_Node_socket_update);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeBitMath"_ustr);
ntype.ui_name = "Bit Math";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.labelfunc = node_label;
ntype.build_multi_function = node_build_multi_function;
ntype.draw_buttons = node_layout;
ntype.gather_link_search_ops = node_gather_link_searches;
ntype.ui_description = "Perform bitwise operations on 32-bit integers";
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace nodes::node_fn_bit_math_cc
} // namespace blender

View File

@@ -0,0 +1,191 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_listbase.h"
#include "BLI_string_utf8.h"
#include "FN_multi_function_registry.hh"
#include "RNA_enum_types.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_inverse_eval_params.hh"
#include "NOD_socket_search_link.hh"
#include "NOD_value_elem_eval.hh"
#include "NOD_rna_define.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_boolean_math_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Bool>("Boolean"_ustr, "Boolean"_ustr);
const bNode *node = b.node_or_null();
if (node != nullptr) {
const auto type = NodeBooleanMathOperation(node->custom1);
if (type != NODE_BOOLEAN_MATH_NOT) {
b.add_input<decl::Bool>("Boolean"_ustr, "Boolean_001"_ustr);
}
}
b.add_output<decl::Bool>("Boolean"_ustr);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_label(const bNodeTree * /*tree*/,
const bNode *node,
char *label,
int label_maxncpy)
{
const char *name;
bool enum_label = RNA_enum_name(rna_enum_node_boolean_math_items, node->custom1, &name);
if (!enum_label) {
name = N_("Unknown");
}
BLI_strncpy_utf8(label, IFACE_(name), label_maxncpy);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
if (!params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_BOOLEAN)) {
return;
}
for (const EnumPropertyItem *item = rna_enum_node_boolean_math_items;
item->identifier != nullptr;
item++)
{
if (item->name != nullptr && item->identifier[0] != '\0') {
NodeBooleanMathOperation operation = static_cast<NodeBooleanMathOperation>(item->value);
params.add_item(IFACE_(item->name), [operation](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeBooleanMath"_ustr);
node.custom1 = operation;
params.update_and_connect_available_socket(node, "Boolean"_ustr);
});
}
}
}
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
{
switch (bnode.custom1) {
case NODE_BOOLEAN_MATH_AND:
return &fn::multi_function::registry::lookup("bool && bool"_ustr);
case NODE_BOOLEAN_MATH_OR:
return &fn::multi_function::registry::lookup("bool || bool"_ustr);
case NODE_BOOLEAN_MATH_NOT:
return &fn::multi_function::registry::lookup("!bool"_ustr);
case NODE_BOOLEAN_MATH_NAND:
return &fn::multi_function::registry::lookup("!(bool && bool)"_ustr);
case NODE_BOOLEAN_MATH_NOR:
return &fn::multi_function::registry::lookup("!(bool || bool)"_ustr);
case NODE_BOOLEAN_MATH_XNOR:
return &fn::multi_function::registry::lookup("bool == bool"_ustr);
case NODE_BOOLEAN_MATH_XOR:
return &fn::multi_function::registry::lookup("bool != bool"_ustr);
case NODE_BOOLEAN_MATH_IMPLY:
return &fn::multi_function::registry::lookup("!bool || bool"_ustr);
case NODE_BOOLEAN_MATH_NIMPLY:
return &fn::multi_function::registry::lookup("bool && !bool"_ustr);
}
BLI_assert_unreachable();
return nullptr;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const mf::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
const NodeBooleanMathOperation op = NodeBooleanMathOperation(params.node.custom1);
switch (op) {
case NODE_BOOLEAN_MATH_NOT: {
params.set_output_elem("Boolean"_ustr, params.get_input_elem<BoolElem>("Boolean"_ustr));
break;
}
default: {
break;
}
}
}
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams &params)
{
using namespace value_elem;
const NodeBooleanMathOperation op = NodeBooleanMathOperation(params.node.custom1);
switch (op) {
case NODE_BOOLEAN_MATH_NOT: {
params.set_input_elem("Boolean"_ustr, params.get_output_elem<BoolElem>("Boolean"_ustr));
break;
}
default: {
break;
}
}
}
static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
{
const NodeBooleanMathOperation op = NodeBooleanMathOperation(params.node.custom1);
const UString first_input_id = "Boolean"_ustr;
const UString output_id = "Boolean"_ustr;
switch (op) {
case NODE_BOOLEAN_MATH_NOT: {
params.set_input(first_input_id, !params.get_output<bool>(output_id));
break;
}
default: {
break;
}
}
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"operation",
"Operation",
"",
rna_enum_node_boolean_math_items,
NOD_inline_enum_accessors(custom1));
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeBooleanMath"_ustr, FN_NODE_BOOLEAN_MATH);
ntype.ui_name = "Boolean Math";
ntype.ui_description = "Perform a logical operation on the given boolean inputs";
ntype.enum_name_legacy = "BOOLEAN_MATH";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.labelfunc = node_label;
ntype.build_multi_function = node_build_multi_function;
ntype.draw_buttons = node_layout;
ntype.gather_link_search_ops = node_gather_link_searches;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;
ntype.eval_inverse = node_eval_inverse;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_boolean_math_cc

View File

@@ -0,0 +1,162 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
#include "BLI_math_color.h"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_rna_define.hh"
#include "RNA_enum_types.hh"
namespace blender::nodes::node_fn_combine_color_cc {
NODE_STORAGE_FUNCS(NodeCombSepColor)
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("Red"_ustr)
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.label_fn([](const bNode &node) {
switch (node_storage(node).mode) {
case NODE_COMBSEP_COLOR_RGB:
default:
return IFACE_("Red");
case NODE_COMBSEP_COLOR_HSV:
case NODE_COMBSEP_COLOR_HSL:
return IFACE_("Hue");
}
});
b.add_input<decl::Float>("Green"_ustr)
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.label_fn([](const bNode &node) {
switch (node_storage(node).mode) {
case NODE_COMBSEP_COLOR_RGB:
default:
return IFACE_("Green");
case NODE_COMBSEP_COLOR_HSV:
case NODE_COMBSEP_COLOR_HSL:
return IFACE_("Saturation");
}
});
b.add_input<decl::Float>("Blue"_ustr)
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.label_fn([](const bNode &node) {
switch (node_storage(node).mode) {
case NODE_COMBSEP_COLOR_RGB:
default:
return IFACE_("Blue");
case NODE_COMBSEP_COLOR_HSV:
return CTX_IFACE_(BLT_I18NCONTEXT_COLOR, "Value");
case NODE_COMBSEP_COLOR_HSL:
return IFACE_("Lightness");
}
});
b.add_input<decl::Float>("Alpha"_ustr)
.default_value(1.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR);
b.add_output<decl::Color>("Color"_ustr);
};
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeCombSepColor *data = MEM_new<NodeCombSepColor>(__func__);
data->mode = NODE_COMBSEP_COLOR_RGB;
node->storage = data;
}
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
{
const NodeCombSepColor &storage = node_storage(bnode);
static auto rgba_fn = mf::build::SI4_SO<float, float, float, float, ColorGeometry4f>(
"RGB", [](float r, float g, float b, float a) { return ColorGeometry4f(r, g, b, a); });
static auto hsva_fn = mf::build::SI4_SO<float, float, float, float, ColorGeometry4f>(
"HSV", [](float h, float s, float v, float a) {
ColorGeometry4f color;
hsv_to_rgb(h, s, v, &color.r, &color.g, &color.b);
color.a = a;
return color;
});
static auto hsla_fn = mf::build::SI4_SO<float, float, float, float, ColorGeometry4f>(
"HSL", [](float h, float s, float l, float a) {
ColorGeometry4f color;
hsl_to_rgb(h, s, l, &color.r, &color.g, &color.b);
color.a = a;
return color;
});
switch (storage.mode) {
case NODE_COMBSEP_COLOR_RGB:
return &rgba_fn;
case NODE_COMBSEP_COLOR_HSV:
return &hsva_fn;
case NODE_COMBSEP_COLOR_HSL:
return &hsla_fn;
}
BLI_assert_unreachable();
return nullptr;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const mf::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"mode",
"Mode",
"Mode of color processing",
rna_enum_node_combsep_color_items,
NOD_storage_enum_accessors(mode));
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeCombineColor"_ustr, FN_NODE_COMBINE_COLOR);
ntype.ui_name = "Combine Color";
ntype.ui_description =
"Combine four channels into a single color, based on a particular color model";
ntype.enum_name_legacy = "COMBINE_COLOR";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
bke::node_type_storage(
ntype, "NodeCombSepColor", node_free_standard_storage, node_copy_standard_storage);
ntype.build_multi_function = node_build_multi_function;
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_combine_color_cc

View File

@@ -0,0 +1,263 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_inverse_eval_params.hh"
#include "NOD_value_elem_eval.hh"
#include "GPU_material.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_combine_matrix_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.use_custom_socket_order();
b.add_output<decl::Matrix>("Matrix"_ustr);
PanelDeclarationBuilder &column_a = b.add_panel("Column 1"_ustr).default_closed(true);
column_a.add_input<decl::Float>("Column 1 Row 1"_ustr).default_value(1.0f);
column_a.add_input<decl::Float>("Column 1 Row 2"_ustr);
column_a.add_input<decl::Float>("Column 1 Row 3"_ustr);
column_a.add_input<decl::Float>("Column 1 Row 4"_ustr);
PanelDeclarationBuilder &column_b = b.add_panel("Column 2"_ustr).default_closed(true);
column_b.add_input<decl::Float>("Column 2 Row 1"_ustr);
column_b.add_input<decl::Float>("Column 2 Row 2"_ustr).default_value(1.0f);
column_b.add_input<decl::Float>("Column 2 Row 3"_ustr);
column_b.add_input<decl::Float>("Column 2 Row 4"_ustr);
PanelDeclarationBuilder &column_c = b.add_panel("Column 3"_ustr).default_closed(true);
column_c.add_input<decl::Float>("Column 3 Row 1"_ustr);
column_c.add_input<decl::Float>("Column 3 Row 2"_ustr);
column_c.add_input<decl::Float>("Column 3 Row 3"_ustr).default_value(1.0f);
column_c.add_input<decl::Float>("Column 3 Row 4"_ustr);
PanelDeclarationBuilder &column_d = b.add_panel("Column 4"_ustr).default_closed(true);
column_d.add_input<decl::Float>("Column 4 Row 1"_ustr);
column_d.add_input<decl::Float>("Column 4 Row 2"_ustr);
column_d.add_input<decl::Float>("Column 4 Row 3"_ustr);
column_d.add_input<decl::Float>("Column 4 Row 4"_ustr).default_value(1.0f);
}
static void copy_with_stride(const IndexMask &mask,
const VArray<float> &src,
const int64_t src_step,
const int64_t src_begin,
const int64_t dst_step,
const int64_t dst_begin,
MutableSpan<float> dst)
{
BLI_assert(src_begin < src_step);
BLI_assert(dst_begin < dst_step);
devirtualize_varray(src, [&](const auto src) {
mask.foreach_index_optimized<int>([&](const int64_t index) {
dst[dst_begin + dst_step * index] = src[src_begin + src_step * index];
});
});
}
class CombineMatrixFunction : public mf::MultiFunction {
public:
CombineMatrixFunction()
{
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Combine Matrix", signature};
builder.single_input<float>("Column 1 Row 1");
builder.single_input<float>("Column 1 Row 2");
builder.single_input<float>("Column 1 Row 3");
builder.single_input<float>("Column 1 Row 4");
builder.single_input<float>("Column 2 Row 1");
builder.single_input<float>("Column 2 Row 2");
builder.single_input<float>("Column 2 Row 3");
builder.single_input<float>("Column 2 Row 4");
builder.single_input<float>("Column 3 Row 1");
builder.single_input<float>("Column 3 Row 2");
builder.single_input<float>("Column 3 Row 3");
builder.single_input<float>("Column 3 Row 4");
builder.single_input<float>("Column 4 Row 1");
builder.single_input<float>("Column 4 Row 2");
builder.single_input<float>("Column 4 Row 3");
builder.single_input<float>("Column 4 Row 4");
builder.single_output<float4x4>("Matrix");
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArray<float> &column_1_row_1 = params.readonly_single_input<float>(0, "Column 1 Row 1");
const VArray<float> &column_1_row_2 = params.readonly_single_input<float>(1, "Column 1 Row 2");
const VArray<float> &column_1_row_3 = params.readonly_single_input<float>(2, "Column 1 Row 3");
const VArray<float> &column_1_row_4 = params.readonly_single_input<float>(3, "Column 1 Row 4");
const VArray<float> &column_2_row_1 = params.readonly_single_input<float>(4, "Column 2 Row 1");
const VArray<float> &column_2_row_2 = params.readonly_single_input<float>(5, "Column 2 Row 2");
const VArray<float> &column_2_row_3 = params.readonly_single_input<float>(6, "Column 2 Row 3");
const VArray<float> &column_2_row_4 = params.readonly_single_input<float>(7, "Column 2 Row 4");
const VArray<float> &column_3_row_1 = params.readonly_single_input<float>(8, "Column 3 Row 1");
const VArray<float> &column_3_row_2 = params.readonly_single_input<float>(9, "Column 3 Row 2");
const VArray<float> &column_3_row_3 = params.readonly_single_input<float>(10,
"Column 3 Row 3");
const VArray<float> &column_3_row_4 = params.readonly_single_input<float>(11,
"Column 3 Row 4");
const VArray<float> &column_4_row_1 = params.readonly_single_input<float>(12,
"Column 4 Row 1");
const VArray<float> &column_4_row_2 = params.readonly_single_input<float>(13,
"Column 4 Row 2");
const VArray<float> &column_4_row_3 = params.readonly_single_input<float>(14,
"Column 4 Row 3");
const VArray<float> &column_4_row_4 = params.readonly_single_input<float>(15,
"Column 4 Row 4");
MutableSpan<float4x4> matrices = params.uninitialized_single_output<float4x4>(16, "Matrix");
MutableSpan<float> components = matrices.cast<float>();
copy_with_stride(mask, column_1_row_1, 1, 0, 16, 0, components);
copy_with_stride(mask, column_1_row_2, 1, 0, 16, 1, components);
copy_with_stride(mask, column_1_row_3, 1, 0, 16, 2, components);
copy_with_stride(mask, column_1_row_4, 1, 0, 16, 3, components);
copy_with_stride(mask, column_2_row_1, 1, 0, 16, 4, components);
copy_with_stride(mask, column_2_row_2, 1, 0, 16, 5, components);
copy_with_stride(mask, column_2_row_3, 1, 0, 16, 6, components);
copy_with_stride(mask, column_2_row_4, 1, 0, 16, 7, components);
copy_with_stride(mask, column_3_row_1, 1, 0, 16, 8, components);
copy_with_stride(mask, column_3_row_2, 1, 0, 16, 9, components);
copy_with_stride(mask, column_3_row_3, 1, 0, 16, 10, components);
copy_with_stride(mask, column_3_row_4, 1, 0, 16, 11, components);
copy_with_stride(mask, column_4_row_1, 1, 0, 16, 12, components);
copy_with_stride(mask, column_4_row_2, 1, 0, 16, 13, components);
copy_with_stride(mask, column_4_row_3, 1, 0, 16, 14, components);
copy_with_stride(mask, column_4_row_4, 1, 0, 16, 15, components);
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const static CombineMatrixFunction fn;
builder.set_matching_fn(fn);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
std::array<std::array<FloatElem, 4>, 4> input_elems;
for (const int col : IndexRange(4)) {
for (const int row : IndexRange(4)) {
const bNodeSocket &socket = params.node.input_socket(col * 4 + row);
input_elems[col][row] = params.get_input_elem<FloatElem>(socket.identifier_ustr());
}
}
MatrixElem matrix_elem;
matrix_elem.translation.x = input_elems[3][0];
matrix_elem.translation.y = input_elems[3][1];
matrix_elem.translation.z = input_elems[3][2];
bool any_inner_3x3 = false;
for (const int col : IndexRange(3)) {
for (const int row : IndexRange(3)) {
any_inner_3x3 |= input_elems[col][row];
}
}
if (any_inner_3x3) {
matrix_elem.rotation = RotationElem::all();
matrix_elem.scale = VectorElem::all();
}
const bool any_non_transform = input_elems[0][3] || input_elems[1][3] || input_elems[2][3] ||
input_elems[3][3];
if (any_non_transform) {
matrix_elem.any_non_transform = FloatElem::all();
}
params.set_output_elem("Matrix"_ustr, matrix_elem);
}
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams &params)
{
using namespace value_elem;
const MatrixElem matrix_elem = params.get_output_elem<MatrixElem>("Matrix"_ustr);
std::array<std::array<FloatElem, 4>, 4> input_elems;
input_elems[3][0] = matrix_elem.translation.x;
input_elems[3][1] = matrix_elem.translation.y;
input_elems[3][2] = matrix_elem.translation.z;
if (matrix_elem.rotation || matrix_elem.scale) {
for (const int col : IndexRange(3)) {
for (const int row : IndexRange(3)) {
input_elems[col][row] = FloatElem::all();
}
}
}
if (matrix_elem.any_non_transform) {
for (const int col : IndexRange(4)) {
input_elems[col][3] = FloatElem::all();
}
}
for (const int col : IndexRange(4)) {
for (const int row : IndexRange(4)) {
const bNodeSocket &socket = params.node.input_socket(col * 4 + row);
params.set_input_elem(socket.identifier_ustr(), input_elems[col][row]);
}
}
}
static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
{
const float4x4 matrix = params.get_output<float4x4>("Matrix"_ustr);
for (const int col : IndexRange(4)) {
for (const int row : IndexRange(4)) {
const bNodeSocket &socket = params.node.input_socket(col * 4 + row);
params.set_input(socket.identifier_ustr(), matrix[col][row]);
}
}
}
static int node_gpu_material(GPUMaterial *material,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *inputs,
GPUNodeStack *outputs)
{
return GPU_stack_link(material, node, "node_function_combine_matrix", inputs, outputs);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeCombineMatrix"_ustr, FN_NODE_COMBINE_MATRIX);
ntype.ui_name = "Combine Matrix";
ntype.ui_description = "Construct a 4x4 matrix from its individual values";
ntype.enum_name_legacy = "COMBINE_MATRIX";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;
ntype.eval_inverse = node_eval_inverse;
ntype.gpu_fn = node_gpu_material;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_combine_matrix_cc

View File

@@ -0,0 +1,130 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "BLI_math_rotation.hh"
#include "NOD_inverse_eval_params.hh"
#include "NOD_value_elem_eval.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_combine_transform_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Translation"_ustr).subtype(PROP_TRANSLATION);
b.add_input<decl::Rotation>("Rotation"_ustr);
b.add_input<decl::Vector>("Scale"_ustr).default_value(float3(1)).subtype(PROP_XYZ);
b.add_output<decl::Matrix>("Transform"_ustr);
}
class CombineTransformFunction : public mf::MultiFunction {
public:
CombineTransformFunction()
{
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Combine Transform", signature};
builder.single_input<float3>("Translation");
builder.single_input<math::Quaternion>("Rotation");
builder.single_input<float3>("Scale");
builder.single_output<float4x4>("Transform");
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArray translation = params.readonly_single_input<float3>(0, "Translation");
const VArray rotation = params.readonly_single_input<math::Quaternion>(1, "Rotation");
const VArray scale = params.readonly_single_input<float3>(2, "Scale");
MutableSpan transforms = params.uninitialized_single_output<float4x4>(3, "Transform");
const std::optional<float3> translation_single = translation.get_if_single();
const std::optional<math::Quaternion> rotation_single = rotation.get_if_single();
const std::optional<float3> scale_single = scale.get_if_single();
const bool no_translation = translation_single && math::is_zero(*translation_single);
const bool no_rotation = rotation_single && math::angle_of(*rotation_single).radian() < 1e-7f;
const bool no_scale = scale_single && math::is_equal(*scale_single, float3(1), 1e-7f);
if (no_rotation && no_scale) {
mask.foreach_index(
[&](const int64_t i) { transforms[i] = math::from_location<float4x4>(translation[i]); });
}
else if (no_translation && no_scale) {
mask.foreach_index(
[&](const int64_t i) { transforms[i] = math::from_rotation<float4x4>(rotation[i]); });
}
else if (no_translation && no_rotation) {
mask.foreach_index(
[&](const int64_t i) { transforms[i] = math::from_scale<float4x4>(scale[i]); });
}
else {
mask.foreach_index([&](const int64_t i) {
transforms[i] = math::from_loc_rot_scale<float4x4>(translation[i], rotation[i], scale[i]);
});
}
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static CombineTransformFunction fn;
builder.set_matching_fn(fn);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
MatrixElem matrix_elem;
matrix_elem.translation = params.get_input_elem<VectorElem>("Translation"_ustr);
matrix_elem.rotation = params.get_input_elem<RotationElem>("Rotation"_ustr);
matrix_elem.scale = params.get_input_elem<VectorElem>("Scale"_ustr);
params.set_output_elem("Transform"_ustr, matrix_elem);
}
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams &params)
{
using namespace value_elem;
const MatrixElem matrix_elem = params.get_output_elem<MatrixElem>("Transform"_ustr);
params.set_input_elem("Translation"_ustr, matrix_elem.translation);
params.set_input_elem("Rotation"_ustr, matrix_elem.rotation);
params.set_input_elem("Scale"_ustr, matrix_elem.scale);
}
static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
{
const float4x4 transform = params.get_output<float4x4>("Transform"_ustr);
float3 translation;
math::Quaternion rotation;
float3 scale;
math::to_loc_rot_scale_safe<true>(transform, translation, rotation, scale);
params.set_input("Translation"_ustr, translation);
params.set_input("Rotation"_ustr, rotation);
params.set_input("Scale"_ustr, scale);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeCombineTransform"_ustr, FN_NODE_COMBINE_TRANSFORM);
ntype.ui_name = "Combine Transform";
ntype.ui_description =
"Combine a translation vector, a rotation, and a scale vector into a transformation matrix";
ntype.enum_name_legacy = "COMBINE_TRANSFORM";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;
ntype.eval_inverse = node_eval_inverse;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_combine_transform_cc

View File

@@ -0,0 +1,850 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cmath>
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_string_utf8.h"
#include "BLT_translation.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "IMB_colormanagement.hh"
#include "RNA_enum_types.hh"
#include "DEG_depsgraph_query.hh"
#include "node_function_util.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "DNA_collection_types.h"
#include "DNA_image_types.h"
#include "DNA_material_types.h"
#include "DNA_object_types.h"
#include "DNA_sound_types.h"
#include "DNA_vfont_types.h"
namespace blender::nodes::node_fn_compare_cc {
NODE_STORAGE_FUNCS(NodeFunctionCompare)
static bool is_supported_data_block_type(const eNodeSocketDatatype data_type)
{
return ELEM(
data_type, SOCK_OBJECT, SOCK_IMAGE, SOCK_COLLECTION, SOCK_MATERIAL, SOCK_FONT, SOCK_SOUND);
}
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
const bNode *node = b.node_or_null();
if (node != nullptr) {
const NodeFunctionCompare &storage = node_storage(*node);
const NodeCompareOperation operation = NodeCompareOperation(storage.operation);
const eNodeSocketDatatype data_type = storage.data_type;
const NodeCompareMode mode = NodeCompareMode(storage.mode);
const bool type_is_float = ELEM(data_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA);
const bool is_vector = data_type == SOCK_VECTOR;
const bool is_data_block = is_supported_data_block_type(data_type);
auto &a_input =
b.add_input(data_type, "A"_ustr).translation_context(BLT_I18NCONTEXT_ID_NODETREE);
auto &b_input =
b.add_input(data_type, "B"_ustr).translation_context(BLT_I18NCONTEXT_ID_NODETREE);
if (data_type == SOCK_STRING || is_data_block) {
a_input.optional_label();
b_input.optional_label();
}
if (is_vector && mode == NODE_COMPARE_MODE_DOT_PRODUCT) {
b.add_input<decl::Float>("C"_ustr).default_value(0.9f);
}
if (is_vector && mode == NODE_COMPARE_MODE_DIRECTION) {
b.add_input<decl::Float>("Angle"_ustr).default_value(0.0872665f).subtype(PROP_ANGLE);
}
if (type_is_float && ELEM(operation, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL)) {
b.add_input<decl::Float>("Epsilon"_ustr).default_value(0.001);
}
}
b.add_output<decl::Bool>("Result"_ustr);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
const NodeFunctionCompare &data = node_storage(*static_cast<const bNode *>(ptr->data));
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
if (data.data_type == SOCK_VECTOR) {
layout.prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
}
layout.prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeFunctionCompare *data = MEM_new<NodeFunctionCompare>(__func__);
data->operation = NODE_COMPARE_GREATER_THAN;
data->data_type = SOCK_FLOAT;
data->mode = NODE_COMPARE_MODE_ELEMENT;
node->storage = data;
}
class SocketSearchOp {
public:
UString socket_name;
eNodeSocketDatatype data_type;
NodeCompareOperation operation;
NodeCompareMode mode = NODE_COMPARE_MODE_ELEMENT;
void operator()(LinkSearchOpParams &params)
{
bNode &node = params.add_node("FunctionNodeCompare"_ustr);
node_storage(node).data_type = data_type;
node_storage(node).operation = operation;
node_storage(node).mode = mode;
params.update_and_connect_available_socket(node, socket_name);
}
};
static std::optional<eNodeSocketDatatype> get_compare_type_for_operation(
const eNodeSocketDatatype type, const NodeCompareOperation operation)
{
switch (type) {
case SOCK_BOOLEAN:
if (ELEM(operation, NODE_COMPARE_COLOR_BRIGHTER, NODE_COMPARE_COLOR_DARKER)) {
return SOCK_RGBA;
}
return SOCK_INT;
case SOCK_INT:
case SOCK_FLOAT:
case SOCK_VECTOR:
if (ELEM(operation, NODE_COMPARE_COLOR_BRIGHTER, NODE_COMPARE_COLOR_DARKER)) {
return SOCK_RGBA;
}
return type;
case SOCK_RGBA:
if (!ELEM(operation,
NODE_COMPARE_COLOR_BRIGHTER,
NODE_COMPARE_COLOR_DARKER,
NODE_COMPARE_EQUAL,
NODE_COMPARE_NOT_EQUAL))
{
return SOCK_VECTOR;
}
return type;
case SOCK_STRING:
if (!ELEM(operation, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL)) {
return std::nullopt;
}
return type;
default:
if (is_supported_data_block_type(type)) {
if (!ELEM(operation, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL)) {
return std::nullopt;
}
return type;
}
return std::nullopt;
}
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const eNodeSocketDatatype type = params.other_socket().type;
if (!ELEM(type, SOCK_INT, SOCK_BOOLEAN, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_STRING) &&
!is_supported_data_block_type(type))
{
return;
}
const UString socket_name = params.in_out() == SOCK_IN ? "A"_ustr : "Result"_ustr;
for (const EnumPropertyItem *item = rna_enum_node_compare_operation_items;
item->identifier != nullptr;
item++)
{
if (item->name != nullptr && item->identifier[0] != '\0') {
const NodeCompareOperation operation = NodeCompareOperation(item->value);
if (const std::optional<eNodeSocketDatatype> fixed_type = get_compare_type_for_operation(
type, operation))
{
params.add_item(IFACE_(item->name), SocketSearchOp{socket_name, *fixed_type, operation});
}
}
}
if (params.in_out() == SOCK_IN && (type != SOCK_STRING || is_supported_data_block_type(type))) {
params.add_item(
IFACE_("Angle"),
SocketSearchOp{
"Angle"_ustr, SOCK_VECTOR, NODE_COMPARE_GREATER_THAN, NODE_COMPARE_MODE_DIRECTION});
}
}
static void node_label(const bNodeTree * /*tree*/,
const bNode *node,
char *label,
int label_maxncpy)
{
const NodeFunctionCompare *data = (NodeFunctionCompare *)node->storage;
const char *name;
bool enum_label = RNA_enum_name(rna_enum_node_compare_operation_items, data->operation, &name);
if (!enum_label) {
name = N_("Unknown");
}
BLI_strncpy_utf8(label, IFACE_(name), label_maxncpy);
}
static float component_average(float3 a)
{
return (a.x + a.y + a.z) / 3.0f;
}
template<typename Fn>
static auto to_static_data_block_type(const eNodeSocketDatatype socket_type, Fn &&fn)
{
switch (socket_type) {
case SOCK_OBJECT:
return fn.template operator()<Object>();
case SOCK_IMAGE:
return fn.template operator()<Image>();
case SOCK_COLLECTION:
return fn.template operator()<Collection>();
case SOCK_MATERIAL:
return fn.template operator()<Material>();
case SOCK_FONT:
return fn.template operator()<VFont>();
case SOCK_SOUND:
return fn.template operator()<bSound>();
default:
BLI_assert_unreachable();
return fn.template operator()<Object>();
}
}
static bool data_blocks_are_equal(const ID *a, const ID *b)
{
return DEG_get_original(a) == DEG_get_original(b);
}
static const mf::MultiFunction *get_multi_function(const bNode &node)
{
const NodeFunctionCompare *data = (NodeFunctionCompare *)node.storage;
const eNodeSocketDatatype data_type = data->data_type;
static auto exec_preset_all = mf::build::exec_presets::AllSpanOrSingle();
static auto exec_preset_first_two = mf::build::exec_presets::SomeSpanOrSingle<0, 1>();
switch (data_type) {
case SOCK_FLOAT:
switch (data->operation) {
case NODE_COMPARE_LESS_THAN: {
static auto fn = mf::build::SI2_SO<float, float, bool>(
"Less Than", [](float a, float b) { return a < b; }, exec_preset_all);
return &fn;
}
case NODE_COMPARE_LESS_EQUAL: {
static auto fn = mf::build::SI2_SO<float, float, bool>(
"Less Equal", [](float a, float b) { return a <= b; }, exec_preset_all);
return &fn;
}
case NODE_COMPARE_GREATER_THAN: {
static auto fn = mf::build::SI2_SO<float, float, bool>(
"Greater Than", [](float a, float b) { return a > b; }, exec_preset_all);
return &fn;
}
case NODE_COMPARE_GREATER_EQUAL: {
static auto fn = mf::build::SI2_SO<float, float, bool>(
"Greater Equal", [](float a, float b) { return a >= b; }, exec_preset_all);
return &fn;
}
case NODE_COMPARE_EQUAL: {
static auto fn = mf::build::SI3_SO<float, float, float, bool>(
"Equal",
[](float a, float b, float epsilon) { return std::abs(a - b) <= epsilon; },
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_NOT_EQUAL: {
static auto fn = mf::build::SI3_SO<float, float, float, bool>(
"Not Equal",
[](float a, float b, float epsilon) { return std::abs(a - b) > epsilon; },
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_COLOR_BRIGHTER:
case NODE_COMPARE_COLOR_DARKER:
break;
}
break;
case SOCK_INT:
switch (data->operation) {
case NODE_COMPARE_LESS_THAN: {
static auto fn = mf::build::SI2_SO<int, int, bool>(
"Less Than", [](int a, int b) { return a < b; }, exec_preset_all);
return &fn;
}
case NODE_COMPARE_LESS_EQUAL: {
static auto fn = mf::build::SI2_SO<int, int, bool>(
"Less Equal", [](int a, int b) { return a <= b; }, exec_preset_all);
return &fn;
}
case NODE_COMPARE_GREATER_THAN: {
static auto fn = mf::build::SI2_SO<int, int, bool>(
"Greater Than", [](int a, int b) { return a > b; }, exec_preset_all);
return &fn;
}
case NODE_COMPARE_GREATER_EQUAL: {
static auto fn = mf::build::SI2_SO<int, int, bool>(
"Greater Equal", [](int a, int b) { return a >= b; }, exec_preset_all);
return &fn;
}
case NODE_COMPARE_EQUAL: {
static auto fn = mf::build::SI2_SO<int, int, bool>(
"Equal", [](int a, int b) { return a == b; }, exec_preset_all);
return &fn;
}
case NODE_COMPARE_NOT_EQUAL: {
static auto fn = mf::build::SI2_SO<int, int, bool>(
"Not Equal", [](int a, int b) { return a != b; }, exec_preset_all);
return &fn;
}
case NODE_COMPARE_COLOR_BRIGHTER:
case NODE_COMPARE_COLOR_DARKER:
break;
}
break;
case SOCK_VECTOR:
switch (data->operation) {
case NODE_COMPARE_LESS_THAN:
switch (data->mode) {
case NODE_COMPARE_MODE_AVERAGE: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Less Than - Average",
[](float3 a, float3 b) { return component_average(a) < component_average(b); },
exec_preset_all);
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Less Than - Dot Product",
[](float3 a, float3 b, float comp) { return math::dot(a, b) < comp; },
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Less Than - Direction",
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) < angle; },
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Less Than - Element-wise",
[](float3 a, float3 b) { return a.x < b.x && a.y < b.y && a.z < b.z; },
exec_preset_all);
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Less Than - Length",
[](float3 a, float3 b) { return math::length(a) < math::length(b); },
exec_preset_all);
return &fn;
}
}
break;
case NODE_COMPARE_LESS_EQUAL:
switch (data->mode) {
case NODE_COMPARE_MODE_AVERAGE: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Less Equal - Average",
[](float3 a, float3 b) { return component_average(a) <= component_average(b); },
exec_preset_all);
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Less Equal - Dot Product",
[](float3 a, float3 b, float comp) { return math::dot(a, b) <= comp; },
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Less Equal - Direction",
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) <= angle; },
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Less Equal - Element-wise",
[](float3 a, float3 b) { return a.x <= b.x && a.y <= b.y && a.z <= b.z; },
exec_preset_all);
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Less Equal - Length",
[](float3 a, float3 b) { return math::length(a) <= math::length(b); },
exec_preset_all);
return &fn;
}
}
break;
case NODE_COMPARE_GREATER_THAN:
switch (data->mode) {
case NODE_COMPARE_MODE_AVERAGE: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Greater Than - Average",
[](float3 a, float3 b) { return component_average(a) > component_average(b); },
exec_preset_all);
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Greater Than - Dot Product",
[](float3 a, float3 b, float comp) { return math::dot(a, b) > comp; },
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Greater Than - Direction",
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) > angle; },
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Greater Than - Element-wise",
[](float3 a, float3 b) { return a.x > b.x && a.y > b.y && a.z > b.z; },
exec_preset_all);
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Greater Than - Length",
[](float3 a, float3 b) { return math::length(a) > math::length(b); },
exec_preset_all);
return &fn;
}
}
break;
case NODE_COMPARE_GREATER_EQUAL:
switch (data->mode) {
case NODE_COMPARE_MODE_AVERAGE: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Greater Equal - Average",
[](float3 a, float3 b) { return component_average(a) >= component_average(b); },
exec_preset_all);
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Greater Equal - Dot Product",
[](float3 a, float3 b, float comp) { return math::dot(a, b) >= comp; },
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Greater Equal - Direction",
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) >= angle; },
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Greater Equal - Element-wise",
[](float3 a, float3 b) { return a.x >= b.x && a.y >= b.y && a.z >= b.z; },
exec_preset_all);
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
"Greater Equal - Length",
[](float3 a, float3 b) { return math::length(a) >= math::length(b); },
exec_preset_all);
return &fn;
}
}
break;
case NODE_COMPARE_EQUAL:
switch (data->mode) {
case NODE_COMPARE_MODE_AVERAGE: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Equal - Average",
[](float3 a, float3 b, float epsilon) {
return abs(component_average(a) - component_average(b)) <= epsilon;
},
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static auto fn = mf::build::SI4_SO<float3, float3, float, float, bool>(
"Equal - Dot Product",
[](float3 a, float3 b, float comp, float epsilon) {
return abs(math::dot(a, b) - comp) <= epsilon;
},
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static auto fn = mf::build::SI4_SO<float3, float3, float, float, bool>(
"Equal - Direction",
[](float3 a, float3 b, float angle, float epsilon) {
return abs(angle_v3v3(a, b) - angle) <= epsilon;
},
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Equal - Element-wise",
[](float3 a, float3 b, float epsilon) {
return abs(a.x - b.x) <= epsilon && abs(a.y - b.y) <= epsilon &&
abs(a.z - b.z) <= epsilon;
},
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Equal - Length",
[](float3 a, float3 b, float epsilon) {
return abs(math::length(a) - math::length(b)) <= epsilon;
},
exec_preset_first_two);
return &fn;
}
}
break;
case NODE_COMPARE_NOT_EQUAL:
switch (data->mode) {
case NODE_COMPARE_MODE_AVERAGE: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Not Equal - Average",
[](float3 a, float3 b, float epsilon) {
return abs(component_average(a) - component_average(b)) > epsilon;
},
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static auto fn = mf::build::SI4_SO<float3, float3, float, float, bool>(
"Not Equal - Dot Product",
[](float3 a, float3 b, float comp, float epsilon) {
return abs(math::dot(a, b) - comp) >= epsilon;
},
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static auto fn = mf::build::SI4_SO<float3, float3, float, float, bool>(
"Not Equal - Direction",
[](float3 a, float3 b, float angle, float epsilon) {
return abs(angle_v3v3(a, b) - angle) > epsilon;
},
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Not Equal - Element-wise",
[](float3 a, float3 b, float epsilon) {
return abs(a.x - b.x) > epsilon || abs(a.y - b.y) > epsilon ||
abs(a.z - b.z) > epsilon;
},
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
"Not Equal - Length",
[](float3 a, float3 b, float epsilon) {
return abs(math::length(a) - math::length(b)) > epsilon;
},
exec_preset_first_two);
return &fn;
}
}
break;
case NODE_COMPARE_COLOR_BRIGHTER:
case NODE_COMPARE_COLOR_DARKER:
break;
}
break;
case SOCK_RGBA:
switch (data->operation) {
case NODE_COMPARE_EQUAL: {
static auto fn = mf::build::SI3_SO<ColorGeometry4f, ColorGeometry4f, float, bool>(
"Equal",
[](ColorGeometry4f a, ColorGeometry4f b, float epsilon) {
return abs(a.r - b.r) <= epsilon && abs(a.g - b.g) <= epsilon &&
abs(a.b - b.b) <= epsilon;
},
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_NOT_EQUAL: {
static auto fn = mf::build::SI3_SO<ColorGeometry4f, ColorGeometry4f, float, bool>(
"Not Equal",
[](ColorGeometry4f a, ColorGeometry4f b, float epsilon) {
return abs(a.r - b.r) > epsilon || abs(a.g - b.g) > epsilon ||
abs(a.b - b.b) > epsilon;
},
exec_preset_first_two);
return &fn;
}
case NODE_COMPARE_COLOR_BRIGHTER: {
static auto fn = mf::build::SI2_SO<ColorGeometry4f, ColorGeometry4f, bool>(
"Brighter",
[](ColorGeometry4f a, ColorGeometry4f b) {
return IMB_colormanagement_get_luminance(a) > IMB_colormanagement_get_luminance(b);
},
exec_preset_all);
return &fn;
}
case NODE_COMPARE_COLOR_DARKER: {
static auto fn = mf::build::SI2_SO<ColorGeometry4f, ColorGeometry4f, bool>(
"Darker",
[](ColorGeometry4f a, ColorGeometry4f b) {
return IMB_colormanagement_get_luminance(a) < IMB_colormanagement_get_luminance(b);
},
exec_preset_all);
return &fn;
}
case NODE_COMPARE_LESS_THAN:
case NODE_COMPARE_LESS_EQUAL:
case NODE_COMPARE_GREATER_THAN:
case NODE_COMPARE_GREATER_EQUAL:
break;
}
break;
case SOCK_STRING:
switch (data->operation) {
case NODE_COMPARE_EQUAL: {
static auto fn = mf::build::SI2_SO<std::string, std::string, bool>(
"Equal", [](std::string a, std::string b) { return a == b; });
return &fn;
}
case NODE_COMPARE_NOT_EQUAL: {
static auto fn = mf::build::SI2_SO<std::string, std::string, bool>(
"Not Equal", [](std::string a, std::string b) { return a != b; });
return &fn;
}
case NODE_COMPARE_LESS_THAN:
case NODE_COMPARE_LESS_EQUAL:
case NODE_COMPARE_GREATER_THAN:
case NODE_COMPARE_GREATER_EQUAL:
case NODE_COMPARE_COLOR_BRIGHTER:
case NODE_COMPARE_COLOR_DARKER:
break;
}
break;
default: {
if (is_supported_data_block_type(data_type)) {
return to_static_data_block_type(
data_type, [&]<typename T>() -> const mf::MultiFunction * {
switch (data->operation) {
case NODE_COMPARE_EQUAL: {
static auto fn = mf::build::SI2_SO<T *, T *, bool>(
"Equal",
[](const T *a, const T *b) {
return data_blocks_are_equal(id_cast<const ID *>(a),
id_cast<const ID *>(b));
},
mf::build::exec_presets::Simple{});
return &fn;
}
case NODE_COMPARE_NOT_EQUAL: {
static auto fn = mf::build::SI2_SO<T *, T *, bool>(
"Not Equal",
[](const T *a, const T *b) {
return !data_blocks_are_equal(id_cast<const ID *>(a),
id_cast<const ID *>(b));
},
mf::build::exec_presets::Simple{});
return &fn;
}
default: {
return nullptr;
}
}
});
}
}
}
return nullptr;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const mf::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
static void data_type_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
bNode *node = static_cast<bNode *>(ptr->data);
NodeFunctionCompare *node_storage = static_cast<NodeFunctionCompare *>(node->storage);
if (node_storage->data_type == SOCK_RGBA && !ELEM(node_storage->operation,
NODE_COMPARE_EQUAL,
NODE_COMPARE_NOT_EQUAL,
NODE_COMPARE_COLOR_BRIGHTER,
NODE_COMPARE_COLOR_DARKER))
{
node_storage->operation = NODE_COMPARE_EQUAL;
}
else if ((node_storage->data_type == SOCK_STRING ||
is_supported_data_block_type(node_storage->data_type)) &&
!ELEM(node_storage->operation, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL))
{
node_storage->operation = NODE_COMPARE_EQUAL;
}
else if (node_storage->data_type != SOCK_RGBA &&
ELEM(node_storage->operation, NODE_COMPARE_COLOR_BRIGHTER, NODE_COMPARE_COLOR_DARKER))
{
node_storage->operation = NODE_COMPARE_EQUAL;
}
rna_Node_socket_update(bmain, scene, ptr);
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem mode_items[] = {
{NODE_COMPARE_MODE_ELEMENT,
"ELEMENT",
0,
"Element-Wise",
"Compare each element of the input vectors"},
{NODE_COMPARE_MODE_LENGTH, "LENGTH", 0, "Length", "Compare the length of the input vectors"},
{NODE_COMPARE_MODE_AVERAGE,
"AVERAGE",
0,
"Average",
"Compare the average of the input vectors elements"},
{NODE_COMPARE_MODE_DOT_PRODUCT,
"DOT_PRODUCT",
0,
"Dot Product",
"Compare the dot products of the input vectors"},
{NODE_COMPARE_MODE_DIRECTION,
"DIRECTION",
0,
"Direction",
"Compare the direction of the input vectors"},
{0, nullptr, 0, nullptr, nullptr},
};
PropertyRNA *prop;
prop = RNA_def_node_enum(
srna,
"operation",
"Operation",
"",
rna_enum_node_compare_operation_items,
NOD_storage_enum_accessors(operation),
NODE_COMPARE_EQUAL,
[](bContext * /*C*/, PointerRNA *ptr, PropertyRNA * /*prop*/, bool *r_free) {
*r_free = true;
bNode *node = static_cast<bNode *>(ptr->data);
NodeFunctionCompare *data = static_cast<NodeFunctionCompare *>(node->storage);
if (ELEM(data->data_type, SOCK_FLOAT, SOCK_INT, SOCK_VECTOR)) {
return enum_items_filter(
rna_enum_node_compare_operation_items, [](const EnumPropertyItem &item) {
return !ELEM(item.value, NODE_COMPARE_COLOR_BRIGHTER, NODE_COMPARE_COLOR_DARKER);
});
}
if (data->data_type == SOCK_STRING) {
return enum_items_filter(
rna_enum_node_compare_operation_items, [](const EnumPropertyItem &item) {
return ELEM(item.value, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL);
});
}
if (data->data_type == SOCK_RGBA) {
return enum_items_filter(rna_enum_node_compare_operation_items,
[](const EnumPropertyItem &item) {
return ELEM(item.value,
NODE_COMPARE_EQUAL,
NODE_COMPARE_NOT_EQUAL,
NODE_COMPARE_COLOR_BRIGHTER,
NODE_COMPARE_COLOR_DARKER);
});
}
if (is_supported_data_block_type(data->data_type)) {
return enum_items_filter(
rna_enum_node_compare_operation_items, [](const EnumPropertyItem &item) {
return ELEM(item.value, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL);
});
}
return enum_items_filter(rna_enum_node_compare_operation_items,
[](const EnumPropertyItem & /*item*/) { return false; });
});
prop = RNA_def_node_enum(
srna,
"data_type",
"Input Type",
"",
rna_enum_node_socket_data_type_items,
NOD_storage_enum_accessors(data_type),
std::nullopt,
[](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
*r_free = true;
return enum_items_filter(
rna_enum_node_socket_data_type_items, [](const EnumPropertyItem &item) {
return ELEM(item.value, SOCK_FLOAT, SOCK_INT, SOCK_VECTOR, SOCK_STRING, SOCK_RGBA) ||
is_supported_data_block_type(eNodeSocketDatatype(item.value));
});
});
RNA_def_property_update_runtime(prop, data_type_update);
prop = RNA_def_node_enum(srna,
"mode",
"Mode",
"",
mode_items,
NOD_storage_enum_accessors(mode),
NODE_COMPARE_MODE_ELEMENT);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeCompare"_ustr, FN_NODE_COMPARE);
ntype.ui_name = "Compare";
ntype.ui_description = "Perform a comparison operation on the two given inputs";
ntype.enum_name_legacy = "COMPARE";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.labelfunc = node_label;
ntype.initfunc = node_init;
bke::node_type_storage(
ntype, "NodeFunctionCompare", node_free_standard_storage, node_copy_standard_storage);
ntype.build_multi_function = node_build_multi_function;
ntype.draw_buttons = node_layout;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_compare_cc

View File

@@ -0,0 +1,83 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_euler.hh"
#include "NOD_inverse_eval_params.hh"
#include "NOD_value_elem_eval.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_euler_to_rotation_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Euler"_ustr).subtype(PROP_EULER);
b.add_output<decl::Rotation>("Rotation"_ustr);
};
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "euler_to_rotation", in, out);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI1_SO<float3, math::Quaternion>(
"Euler XYZ to Quaternion",
[](float3 euler) { return math::to_quaternion(math::EulerXYZ(euler)); });
builder.set_matching_fn(fn);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
RotationElem rotation_elem;
rotation_elem.euler = params.get_input_elem<VectorElem>("Euler"_ustr);
if (rotation_elem) {
rotation_elem.axis = VectorElem::all();
rotation_elem.angle = FloatElem::all();
}
params.set_output_elem("Rotation"_ustr, rotation_elem);
}
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams &params)
{
using namespace value_elem;
const RotationElem rotation_elem = params.get_output_elem<RotationElem>("Rotation"_ustr);
VectorElem vector_elem = rotation_elem.euler;
params.set_input_elem("Euler"_ustr, vector_elem);
}
static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
{
const math::Quaternion rotation = params.get_output<math::Quaternion>("Rotation"_ustr);
params.set_input("Euler"_ustr, float3(math::to_euler(rotation)));
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeEulerToRotation"_ustr, FN_NODE_EULER_TO_ROTATION);
ntype.ui_name = "Euler to Rotation";
ntype.ui_description = "Build a rotation from separate angles around each axis";
ntype.enum_name_legacy = "EULER_TO_ROTATION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;
ntype.eval_inverse = node_eval_inverse;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_euler_to_rotation_cc

View File

@@ -0,0 +1,99 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string_utf8.h"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_find_in_string_cc {
enum class Mode {
FirstFromStart = 0,
FirstFromEnd = 1,
};
static const EnumPropertyItem mode_items[] = {
{int(Mode::FirstFromStart),
"FROM_START",
0,
N_("From Start"),
N_("Find the first occurrence of the string")},
{int(Mode::FirstFromEnd),
"FROM_END",
0,
N_("From End"),
N_("Find the last occurrence of the string")},
{},
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::String>("String"_ustr).optional_label();
b.add_input<decl::String>("Search"_ustr);
b.add_input<decl::Menu>("Mode"_ustr).static_items(mode_items).optional_label();
b.add_output<decl::Int>("First Found"_ustr);
b.add_output<decl::Int>("Count"_ustr);
}
static int string_find(const StringRef text, const StringRef token, const bool from_end)
{
if (text.is_empty() || token.is_empty()) {
return 0;
}
const int pos = from_end ? text.rfind(token) : text.find(token, 0);
size_t r_len_bytes;
const int pos_n = BLI_strnlen_utf8_ex(text.data(), pos, &r_len_bytes);
return pos_n;
}
static int string_count(const StringRef text, const StringRef token)
{
if (text.is_empty() || token.is_empty()) {
return 0;
}
int count = 0;
const int match_len = token.size();
int pos = 0;
while ((pos = text.find(token, pos)) != StringRef::not_found) {
count++;
pos += match_len;
}
return count;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto token_position_count =
mf::build::SI3_SO2<std::string, std::string, MenuValue, int, int>(
"Find in String",
[](const std::string &text,
const std::string &token,
const MenuValue mode,
int &first,
int &count) -> void {
first = string_find(text, token, mode == Mode::FirstFromEnd);
count = string_count(text, token);
});
builder.set_matching_fn(&token_position_count);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeFindInString"_ustr, FN_NODE_FIND_IN_STRING);
ntype.ui_name = "Find in String";
ntype.ui_description =
"Find the number of times a given string occurs in another string and the position of the "
"first match";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_find_in_string_cc

View File

@@ -0,0 +1,94 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cmath>
#include "BLI_string_utf8.h"
#include "RNA_enum_types.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_float_to_int_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("Float"_ustr);
b.add_output<decl::Int>("Integer"_ustr);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "rounding_mode", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_label(const bNodeTree * /*tree*/,
const bNode *node,
char *label,
int label_maxncpy)
{
const char *name;
bool enum_label = RNA_enum_name(rna_enum_node_float_to_int_items, node->custom1, &name);
if (!enum_label) {
name = CTX_N_(BLT_I18NCONTEXT_ID_NODETREE, "Unknown");
}
BLI_strncpy_utf8(label, CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, name), label_maxncpy);
}
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
{
static auto exec_preset = mf::build::exec_presets::AllSpanOrSingle();
static auto round_fn = mf::build::SI1_SO<float, int>(
"Round", [](float a) { return int(round(a)); }, exec_preset);
static auto floor_fn = mf::build::SI1_SO<float, int>(
"Floor", [](float a) { return int(floor(a)); }, exec_preset);
static auto ceil_fn = mf::build::SI1_SO<float, int>(
"Ceiling", [](float a) { return int(ceil(a)); }, exec_preset);
static auto trunc_fn = mf::build::SI1_SO<float, int>(
"Truncate", [](float a) { return int(trunc(a)); }, exec_preset);
switch (static_cast<FloatToIntRoundingMode>(bnode.custom1)) {
case FN_NODE_FLOAT_TO_INT_ROUND:
return &round_fn;
case FN_NODE_FLOAT_TO_INT_FLOOR:
return &floor_fn;
case FN_NODE_FLOAT_TO_INT_CEIL:
return &ceil_fn;
case FN_NODE_FLOAT_TO_INT_TRUNCATE:
return &trunc_fn;
}
BLI_assert_unreachable();
return nullptr;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const mf::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeFloatToInt"_ustr, FN_NODE_FLOAT_TO_INT);
ntype.ui_name = "Float to Integer";
ntype.ui_description =
"Convert the given floating-point number to an integer, with a choice of methods";
ntype.enum_name_legacy = "FLOAT_TO_INT";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.labelfunc = node_label;
ntype.build_multi_function = node_build_multi_function;
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_float_to_int_cc

View File

@@ -0,0 +1,883 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <charconv>
#include <fmt/format.h>
#include <regex>
#include "RNA_enum_types.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "BLO_read_write.hh"
#include "NOD_fn_format_string.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
#include "NOD_socket_items_blend.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "BKE_path_templates.hh"
#include "node_function_util.hh"
namespace blender {
namespace nodes::node_fn_format_string_cc {
NODE_STORAGE_FUNCS(NodeFunctionFormatString)
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::String>("Format"_ustr)
.optional_label()
.description(
"Format string using a Python and path template compatible syntax. For example, "
"\"Count: "
"{}\" would replace the {} with the first input value.");
b.add_output<decl::String>("String"_ustr).align_with_previous();
const bNodeTree *ntree = b.tree_or_null();
const bNode *node = b.node_or_null();
if (!ntree || !node) {
return;
}
const NodeFunctionFormatString &storage = node_storage(*node);
for (const int i : IndexRange(storage.items_num)) {
const NodeFunctionFormatStringItem &item = storage.items[i];
const eNodeSocketDatatype socket_type = item.socket_type;
const UString name(item.name);
const std::string identifier = FormatStringItemsAccessor::socket_identifier_for_item(item);
b.add_input(socket_type, name, UString(identifier))
.socket_name_ptr(&ntree->id, *FormatStringItemsAccessor::item_srna, &item, "name");
}
b.add_input<decl::Extend>(""_ustr, "__extend__"_ustr)
.custom_draw(socket_items::ui::draw_extend_socket_fn<FormatStringItemsAccessor>());
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeFunctionFormatString *data = MEM_new<NodeFunctionFormatString>(__func__);
node->storage = data;
}
static void node_copy_storage(bNodeTree * /*tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeFunctionFormatString &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_new<NodeFunctionFormatString>(__func__, dna::shallow_copy(src_storage));
dst_node->storage = dst_storage;
socket_items::copy_array<FormatStringItemsAccessor>(*src_node, *dst_node);
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<FormatStringItemsAccessor>(*node);
MEM_delete(static_cast<NodeFunctionFormatString *>(node->storage));
}
static bool node_insert_link(bke::NodeInsertLinkParams &params)
{
return socket_items::try_add_item_via_any_extend_socket<FormatStringItemsAccessor>(
params.ntree, params.node, params.node, params.link);
}
static void node_operators()
{
socket_items::ops::make_common_operators<FormatStringItemsAccessor>();
}
static void node_layout_ex(ui::Layout &layout, bContext *C, PointerRNA *ptr)
{
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode &node = *ptr->data_as<bNode>();
if (ui::Layout *panel = layout.panel(C, "format_string_items", false, IFACE_("Format Items"))) {
socket_items::ui::draw_items_list_with_operators<FormatStringItemsAccessor>(
C, panel, tree, node);
socket_items::ui::draw_active_item_props<FormatStringItemsAccessor>(
tree, node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
});
}
}
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
{
socket_items::blend_write<FormatStringItemsAccessor>(&writer, node);
}
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
{
socket_items::blend_read_data<FormatStringItemsAccessor>(&reader, node);
}
static std::optional<StringRef> find_format_specifier(const StringRef format)
{
BLI_assert(format[0] == '{');
int64_t braces_depth = 1;
for (const char &c : format.substr(1)) {
if (c == '{') {
braces_depth++;
}
else if (c == '}') {
braces_depth--;
}
if (braces_depth == 0) {
const int length = &c - format.data() + 1;
return format.substr(0, length);
}
}
return std::nullopt;
}
static int64_t find_next_format_start_or_end(const StringRef format,
const int64_t start,
std::string &r_out)
{
int64_t i = start;
while (i < format.size()) {
const char c = format[i];
switch (c) {
case '{':
case '}': {
if (i + 1 < format.size()) {
const char next_c = format[i + 1];
if (next_c == c) {
i += 2;
r_out += c;
continue;
}
}
return i;
}
default: {
r_out += c;
i++;
break;
}
}
}
return format.size();
}
struct FormatPatternInfo {
std::string pattern_str;
std::regex pattern;
int width_group;
std::optional<int> precision_group;
};
/** Also see https://fmt.dev/latest/syntax/. */
static FormatPatternInfo get_pattern_by_type_impl(const CPPType &type)
{
std::string pattern;
int groups_num = 0;
/* Beginning of string. */
pattern += '^';
/* Fill and Align. */
pattern += "([^{}]?[<>^])?";
groups_num += 1;
if (type.is<float>() || type.is<int>()) {
/* Sign. */
pattern += "[+\\- ]?";
/* '#' for alternate form is omitted for better potential future compatibility with
* path templates (#BKE_path_apply_template). */
/* Sign-aware zero padding. */
pattern += "0?";
}
/* A width cannot start with 0, as 0 is parsed as the padding flag. */
const std::string width_integer_or_identifier = "([1-9]\\d*|(\\{.*\\}))";
pattern += width_integer_or_identifier;
pattern += "?";
groups_num += 2;
const int width_group = groups_num;
std::optional<int> precision_group;
if (type.is<float>() || type.is<std::string>()) {
/* Precision is allowed to be 0. */
const std::string precision_integer_or_identifier = "(\\d+|(\\{.*\\}))";
pattern += "(\\.";
pattern += precision_integer_or_identifier;
pattern += ")?";
groups_num += 3;
precision_group = groups_num;
}
/* "L" is omitted, because we take the current locale into account in Geometry Nodes. */
/* Allowed type specifiers vary by data type. */
if (type.is<std::string>()) {
pattern += "[s\\?]?";
}
else if (type.is<int>()) {
pattern += "[bBcdoxX]?";
}
else if (type.is<float>()) {
pattern += "[aAeEfFgG]?";
}
/* End of string. */
pattern += '$';
return {pattern, std::regex{pattern}, width_group, precision_group};
}
static const FormatPatternInfo *get_pattern_by_type(const CPPType &type)
{
if (type.is<float>()) {
static FormatPatternInfo info = get_pattern_by_type_impl(CPPType::get<float>());
return &info;
}
if (type.is<int>()) {
static FormatPatternInfo info = get_pattern_by_type_impl(CPPType::get<int>());
return &info;
}
if (type.is<std::string>()) {
static FormatPatternInfo info = get_pattern_by_type_impl(CPPType::get<std::string>());
return &info;
}
return nullptr;
}
class FormatInputsLookup {
private:
const Span<GVArray> inputs_;
const VectorSet<std::string> &input_names_;
int64_t next_auto_index_ = 0;
/**
* Once the first non-auto-index is used, it's not allowed to use the auto-index afterwards
* anymore.
*/
bool non_auto_index_used_ = false;
public:
FormatInputsLookup(const Span<GVArray> inputs, const VectorSet<std::string> &input_names)
: inputs_(inputs), input_names_(input_names)
{
}
const GVArray *find_next_input(const StringRef identifier, std::optional<std::string> &r_error)
{
const std::optional<int64_t> input_index = this->find_next_input_index(identifier, r_error);
if (!input_index.has_value()) {
return nullptr;
}
return &inputs_[*input_index];
}
std::optional<int64_t> find_next_input_index(const StringRef identifier,
std::optional<std::string> &r_error)
{
if (identifier.is_empty()) {
if (non_auto_index_used_) {
/* Once the first explicit identifier is used, it's not allowed to use the auto-index
* anymore. Only other explicit identifiers are allowed. */
if (!r_error) {
r_error = TIP_(
"Empty identifier cannot be used when explicit identifier was used before. For "
"example, \"{} {x}\" is ok but \"{x} {}\" is not.");
}
return std::nullopt;
}
if (next_auto_index_ == inputs_.size()) {
/* Not enough inputs provided. */
if (!r_error) {
r_error = TIP_("Format uses more inputs than provided.");
}
return std::nullopt;
}
return next_auto_index_++;
}
non_auto_index_used_ = true;
if (std::isdigit(identifier[0])) {
int64_t index;
std::from_chars_result res = std::from_chars(identifier.begin(), identifier.end(), index);
if (res.ec != std::errc()) {
if (!r_error) {
r_error = fmt::format(fmt::runtime(TIP_("Invalid identifier: \"{}\"")), identifier);
}
return std::nullopt;
}
if (res.ptr < identifier.end()) {
/* There are other characters after the number. */
if (!r_error) {
r_error = fmt::format(
fmt::runtime(TIP_("An input name cannot start with a digit: \"{}\"")), identifier);
}
return std::nullopt;
}
if (index >= inputs_.size()) {
if (!r_error) {
if (inputs_.is_empty()) {
r_error = fmt::format(fmt::runtime(TIP_("There are no inputs.")), identifier);
}
else {
r_error = fmt::format(
fmt::runtime(TIP_("Input with index {} does not exist. Currently, the maximum "
"possible index is {}. Did you mean to use {{:{}}}?")),
identifier,
inputs_.size() - 1,
identifier);
}
}
return std::nullopt;
}
return index;
}
const int index = input_names_.index_of_try_as(identifier);
if (index == -1) {
if (!r_error) {
r_error = fmt::format(fmt::runtime(TIP_("Input does not exist: \"{}\"")), identifier);
}
return std::nullopt;
}
return index;
}
};
struct ProcessedPythonCompatibleFormat {
const GVArray *widths = nullptr;
const GVArray *precisions = nullptr;
/**
* This is compatible with the C++ fmt library.
* It formats exactly one value and may use a dynamic width or precision.
*/
std::string fmt_format_str;
};
static std::string create_invalid_python_compatible_format_error(const StringRef format,
const StringRef format_outer,
const FormatPatternInfo &pattern)
{
for (const char c : format) {
if (pattern.pattern_str.find(c) == std::string::npos && std::isprint(c) && !std::isdigit(c)) {
return fmt::format(
fmt::runtime(TIP_("Format contains unsupported \"{}\" character: \"{}\"")),
c,
format_outer);
}
}
return fmt::format(fmt::runtime(TIP_("Invalid format: \"{}\"")), format_outer);
}
static std::optional<ProcessedPythonCompatibleFormat> preprocess_python_compatible_syntax(
const StringRef format,
const StringRef format_outer,
const CPPType &type,
FormatInputsLookup &inputs_lookup,
std::optional<std::string> &r_error)
{
const FormatPatternInfo *allowed_pattern = get_pattern_by_type(type);
if (!allowed_pattern) {
/* The type can't be formatted. The user shouldn't be able to trigger this error but nice to
* handle it anyway. */
if (!r_error) {
r_error = fmt::format(fmt::runtime(TIP_("Type \"{}\" cannot be formatted")), type.name());
}
return std::nullopt;
}
/* Check the syntax of the format string with what is allowed. */
std::cmatch m;
if (!std::regex_search(format.begin(), format.end(), m, allowed_pattern->pattern)) {
if (!r_error) {
r_error = create_invalid_python_compatible_format_error(
format, format_outer, *allowed_pattern);
}
return std::nullopt;
}
ProcessedPythonCompatibleFormat result;
/* Identifiers that are used to specify the width or precision will be replaced with {}. */
Vector<std::string> formats_to_replace;
/* Check if a dynamic width is specified. */
const std::string width_outer = m.str(allowed_pattern->width_group);
if (!width_outer.empty()) {
const StringRef width_inner = StringRef(width_outer).drop_prefix(1).drop_suffix(1);
result.widths = inputs_lookup.find_next_input(width_inner, r_error);
if (!result.widths) {
return std::nullopt;
}
if (!result.widths->type().is<int>()) {
if (!r_error) {
r_error = fmt::format(
fmt::runtime(TIP_("Only integer inputs can be used as dynamic width: \"{}\"")),
format_outer);
}
return std::nullopt;
}
formats_to_replace.append(width_outer);
}
/* Check if a dynamic precision is specified. */
if (allowed_pattern->precision_group.has_value()) {
const std::string precision_outer = m.str(*allowed_pattern->precision_group);
if (!precision_outer.empty()) {
const StringRef precision_inner = StringRef(precision_outer).drop_prefix(1).drop_suffix(1);
result.precisions = inputs_lookup.find_next_input(precision_inner, r_error);
if (!result.precisions) {
return std::nullopt;
}
if (!result.precisions->type().is<int>()) {
if (!r_error) {
r_error = fmt::format(
fmt::runtime(TIP_("Only integer inputs can be used as dynamic precision: \"{}\"")),
format_outer);
}
return std::nullopt;
}
formats_to_replace.append(precision_outer);
}
}
result.fmt_format_str = "{:";
result.fmt_format_str.append(format.begin(), format.end());
result.fmt_format_str += '}';
/* Replace identifiers with {}, because the source identifiers are not passed to fmt. */
for (const std::string &old : formats_to_replace) {
const int64_t old_start = result.fmt_format_str.find(old);
if (old_start != std::string::npos) {
result.fmt_format_str.replace(old_start, old.size(), "{}");
}
}
return result;
}
static void format_with_fmt(const fmt::runtime_format_string<> format,
const GVArray &input,
const GVArray *widths,
const GVArray *precisions,
const IndexMask &mask,
MutableSpan<std::string> r_formatted_strings)
{
const auto append_single_formatted_string = [&](const auto &varray) {
mask.foreach_index([&](const int64_t i) {
std::string &output = r_formatted_strings[i];
auto output_inserter = std::back_inserter(output);
try {
if (precisions) {
const int precision = std::max(0, precisions->get<int>(i));
if (widths) {
const int width = std::max(0, widths->get<int>(i));
fmt::format_to(output_inserter, format, varray[i], width, precision);
}
else {
fmt::format_to(output_inserter, format, varray[i], precision);
}
}
else {
if (widths) {
const int width = std::max(0, widths->get<int>(i));
fmt::format_to(output_inserter, format, varray[i], width);
}
else {
fmt::format_to(output_inserter, format, varray[i]);
}
}
}
catch (const fmt::format_error & /*error*/) {
/* Invalid patterns should have been caught before already. */
BLI_assert_unreachable();
}
});
};
const CPPType &type = input.type();
if (type.is<float>()) {
append_single_formatted_string(input.typed<float>());
}
else if (type.is<int>()) {
append_single_formatted_string(input.typed<int>());
}
else if (type.is<std::string>()) {
append_single_formatted_string(input.typed<std::string>());
}
else {
/* The input type should have been checked earlier already. */
BLI_assert_unreachable();
}
}
static void format_with_python_compatible_syntax(const StringRef format_pattern,
const StringRef format_outer,
const GVArray &input,
const IndexMask &mask,
FormatInputsLookup &inputs_lookup,
MutableSpan<std::string> r_formatted_strings,
std::optional<std::string> &r_error)
{
const CPPType &type = input.type();
/* Extract information like width and precision inputs. */
std::optional<ProcessedPythonCompatibleFormat> processed_format =
preprocess_python_compatible_syntax(
format_pattern, format_outer, type, inputs_lookup, r_error);
if (!processed_format.has_value()) {
BLI_assert(r_error);
return;
}
format_with_fmt(fmt::runtime(processed_format->fmt_format_str),
input,
processed_format->widths,
processed_format->precisions,
mask,
r_formatted_strings);
}
static void format_with_hash_syntax(const StringRef format_pattern,
const GVArray &input,
const IndexMask &mask,
MutableSpan<std::string> r_formatted_strings,
std::optional<std::string> &r_error)
{
const CPPType &type = input.type();
if (type.is<float>()) {
mask.foreach_index([&](const int64_t i) {
std::string &output = r_formatted_strings[i];
const float value = input.get<float>(i);
if (const std::optional<std::string> value_str = BKE_path_template_format_float(
format_pattern, value))
{
output.append(*value_str);
}
else if (!r_error) {
r_error = fmt::format(fmt::runtime(TIP_("Invalid format specifier: \"{}\"")),
format_pattern);
}
});
}
else if (type.is<int>()) {
mask.foreach_index([&](const int64_t i) {
std::string &output = r_formatted_strings[i];
const int64_t value = input.get<int>(i);
if (const std::optional<std::string> value_str = BKE_path_template_format_int(format_pattern,
value))
{
output.append(*value_str);
}
else if (!r_error) {
r_error = fmt::format(fmt::runtime(TIP_("Invalid format specifier: \"{}\"")),
format_pattern);
}
});
}
else if (type.is<std::string>()) {
if (!r_error) {
r_error = fmt::format(fmt::runtime(TIP_("Invalid format specifier for string: \"{}\"")),
format_pattern);
}
}
else if (!r_error) {
r_error = fmt::format(fmt::runtime(TIP_("Type \"{}\" cannot be formatted")), type.name());
}
}
static void format_without_format_specifier(const GVArray &input,
const IndexMask &mask,
MutableSpan<std::string> r_formatted_strings,
std::optional<std::string> &r_error)
{
const CPPType &type = input.type();
if (type.is<float>()) {
mask.foreach_index([&](const int64_t i) {
const float value = input.get<float>(i);
std::string &output = r_formatted_strings[i];
std::string value_str = fmt::format("{}", value);
/* Add ".0" if there are no decimals yet to match Python. */
if (StringRef(value_str).find_first_not_of("-0123456789") == StringRef::not_found) {
value_str.append(".0");
}
output += value_str;
});
}
else if (type.is<int>()) {
mask.foreach_index([&](const int64_t i) {
const int64_t value = input.get<int>(i);
std::string &output = r_formatted_strings[i];
output += fmt::format("{}", value);
});
}
else if (type.is<std::string>()) {
mask.foreach_index([&](const int64_t i) {
const std::string value = input.get<std::string>(i);
std::string &output = r_formatted_strings[i];
output += value;
});
}
else if (!r_error) {
r_error = fmt::format(fmt::runtime(TIP_("Type \"{}\" cannot be formatted")), type.name());
}
}
static bool format_strings(const StringRef format,
const Span<GVArray> inputs,
const VectorSet<std::string> &input_names,
const IndexMask &mask,
MutableSpan<std::string> r_formatted_strings,
std::optional<std::string> &r_error)
{
CPPType::get<std::string>().value_initialize_indices(r_formatted_strings.data(), mask);
FormatInputsLookup inputs_lookup{inputs, input_names};
int64_t current_index = 0;
while (current_index < format.size()) {
/* Find the string until the next format starts or the string ends. */
std::string copy_str;
const int64_t next_format_start_or_end = find_next_format_start_or_end(
format, current_index, copy_str);
/* Append the non-formatted string to the outputs. */
if (!copy_str.empty()) {
mask.foreach_index([&](const int64_t i) {
std::string &output = r_formatted_strings[i];
output.append(copy_str);
});
}
/* The string has ended, so return successfully. */
if (next_format_start_or_end == format.size()) {
break;
}
current_index = next_format_start_or_end;
/* Find the format specifier starting at the current index. */
const std::optional<StringRef> format_outer = find_format_specifier(
format.substr(current_index));
if (!format_outer.has_value()) {
if (!r_error) {
r_error = fmt::format(fmt::runtime(TIP_("Format specifier is not closed: \"{}\"")),
format.substr(current_index));
}
return false;
}
const StringRef format_inner = format_outer->substr(1, format_outer->size() - 2);
/* Extract the identifier and the pattern which are split by a colon. */
StringRef identifier;
StringRef format_pattern;
const int64_t colon_index = format_inner.find(':');
if (colon_index == StringRef::not_found) {
identifier = format_inner;
}
else {
identifier = format_inner.substr(0, colon_index);
format_pattern = format_inner.substr(colon_index + 1);
}
/* Find the typed input values and get the corresponding allowed pattern. */
const GVArray *input = inputs_lookup.find_next_input(identifier, r_error);
if (!input) {
return false;
}
if (format_pattern.is_empty()) {
format_without_format_specifier(*input, mask, r_formatted_strings, r_error);
}
else if (format_pattern.find('#') == StringRef::not_found) {
format_with_python_compatible_syntax(format_pattern,
*format_outer,
*input,
mask,
inputs_lookup,
r_formatted_strings,
r_error);
}
else {
format_with_hash_syntax(format_pattern, *input, mask, r_formatted_strings, r_error);
}
if (r_error) {
return false;
}
current_index += format_outer->size();
}
return true;
}
class FormatStringMultiFunction : public mf::MultiFunction {
private:
/** Take ownership of the tree because it contains the node. */
std::shared_ptr<const bNodeTree> shared_tree_;
const bNode &node_;
VectorSet<std::string> input_names_;
mf::Signature signature_;
public:
FormatStringMultiFunction(const bNode &node, std::shared_ptr<const bNodeTree> shared_tree)
: shared_tree_(std::move(shared_tree)), node_(node)
{
const NodeFunctionFormatString &storage = node_storage(node);
mf::SignatureBuilder builder{"Format String", signature_};
builder.single_input<std::string>("Format");
for (const int i : IndexRange(storage.items_num)) {
const NodeFunctionFormatStringItem &item = storage.items[i];
const eNodeSocketDatatype socket_type = item.socket_type;
const CPPType &type = *bke::socket_type_to_geo_nodes_base_cpp_type(socket_type);
builder.single_input(item.name, type);
input_names_.add_new(StringRef(item.name));
}
builder.single_output<std::string>("String");
this->set_signature(&signature_);
}
void call(const IndexMask &mask, mf::Params params, mf::Context context) const override
{
const NodeFunctionFormatString &storage = node_storage(node_);
const VArray<std::string> formats = params.readonly_single_input<std::string>(0, "Format");
MutableSpan<std::string> outputs = params.uninitialized_single_output<std::string>(
storage.items_num + 1, "String");
Array<GVArray> inputs(storage.items_num);
for (const int i : IndexRange(storage.items_num)) {
inputs[i] = params.readonly_single_input(i + 1);
}
std::optional<std::string> error_message;
if (const std::optional<std::string> single_format = formats.get_if_single()) {
if (!format_strings(*single_format, inputs, input_names_, mask, outputs, error_message)) {
mask.foreach_index([&](const int64_t i) { outputs[i].clear(); });
}
}
else {
mask.foreach_index(
[&](const int64_t i) {
const std::string format = formats[i];
if (!format_strings(format,
inputs,
input_names_,
IndexRange::from_single(i),
outputs,
error_message))
{
outputs[i].clear();
}
},
exec_mode::grain_size(256));
}
if (error_message.has_value()) {
report_from_multi_function(context, NodeWarningType::Error, std::move(*error_message));
}
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
builder.construct_and_set_matching_fn<FormatStringMultiFunction>(builder.node(),
builder.shared_tree());
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeFormatString"_ustr);
ntype.ui_name = "Format String";
ntype.ui_description =
"Insert values into a string using a Python and path template compatible formatting syntax";
ntype.nclass = NODE_CLASS_CONVERTER;
bke::node_type_storage(ntype, "NodeFunctionFormatString", node_free_storage, node_copy_storage);
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.initfunc = node_init;
ntype.draw_buttons_ex = node_layout_ex;
ntype.insert_link = node_insert_link;
ntype.register_operators = node_operators;
ntype.blend_write_storage_content = node_blend_write;
ntype.blend_data_read_storage_content = node_blend_read;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace nodes::node_fn_format_string_cc
namespace nodes {
StructRNA **FormatStringItemsAccessor::item_srna = &RNA_NodeFunctionFormatStringItem;
void FormatStringItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
writer->write_string(item.name);
}
void FormatStringItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
std::string FormatStringItemsAccessor::custom_initial_name(const bNode &node, StringRef src_name)
{
/* The goal is to find a single-letter name that is not used already. Ideally, it starts with the
* same letter as the given name. */
const auto &storage = *static_cast<NodeFunctionFormatString *>(node.storage);
char initial = 'a';
if (!src_name.is_empty()) {
const char first_c = src_name[0];
if (first_c >= 'a' && first_c <= 'z') {
initial = first_c;
}
else if (first_c >= 'A' && first_c <= 'Z') {
initial = first_c - 'A' + 'a';
}
}
for (const int i : IndexRange('z' - 'a' + 1)) {
char c = initial + i;
if (c > 'z') {
/* Start at 'a' again. */
c = c - 'z' + 'a' - 1;
}
const std::string potential_name = std::string(1, c);
const bool name_exists = std::any_of(
storage.items,
storage.items + storage.items_num,
[&](const NodeFunctionFormatStringItem &item) { return item.name == potential_name; });
if (!name_exists) {
return potential_name;
}
}
return src_name;
}
std::string FormatStringItemsAccessor::validate_name(const StringRef name)
{
/* The name has to start with a letter or underscore. The remaining letters may additionally be
* digits. */
std::string result;
if (name.is_empty()) {
return result;
}
const char first_char = name[0];
if (!std::isalpha(first_char) && first_char != '_') {
result += '_';
}
for (const char c : name) {
if (std::isalnum(c) || c == '_') {
result += c;
}
if (ELEM(c, '-', '.', ' ', '\t')) {
result += '_';
}
}
return result;
}
} // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,194 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_hash.h"
#include "BLI_math_matrix_types.hh"
#include "BLI_noise.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_enum_types.hh"
#include "node_function_util.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_fn_hash_value_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
const bNode *node = b.node_or_null();
if (node) {
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
b.add_input(data_type, "Value"_ustr);
}
b.add_input<decl::Int>("Seed"_ustr, "Seed"_ustr);
b.add_output<decl::Int>("Hash"_ustr);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = SOCK_INT;
}
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
{
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(bnode.custom1);
static auto exec_preset = mf::build::exec_presets::AllSpanOrSingle();
static auto fn_hash_float = mf::build::SI2_SO<float, int, int>(
"Hash Float",
[](float a, int seed) { return noise::hash(noise::hash_float(a), seed); },
exec_preset);
static auto fn_hash_vector = mf::build::SI2_SO<float3, int, int>(
"Hash Vector",
[](float3 a, int seed) { return noise::hash(noise::hash_float(a), seed); },
exec_preset);
static auto fn_hash_color = mf::build::SI2_SO<ColorGeometry4f, int, int>(
"Hash Color",
[](ColorGeometry4f a, int seed) { return noise::hash(noise::hash_float(float4(a)), seed); },
exec_preset);
static auto fn_hash_int = mf::build::SI2_SO<int, int, int>(
"Hash Integer",
[](int a, int seed) { return noise::hash(noise::hash(a), seed); },
exec_preset);
static auto fn_hash_string = mf::build::SI2_SO<std::string, int, int>(
"Hash String",
[](std::string a, int seed) { return noise::hash(BLI_hash_string(a.c_str()), seed); },
exec_preset);
static auto fn_hash_rotation = mf::build::SI2_SO<math::Quaternion, int, int>(
"Hash Rotation",
[](math::Quaternion a, int seed) { return noise::hash(noise::hash_float(float4(a)), seed); },
exec_preset);
static auto fn_hash_matrix = mf::build::SI2_SO<float4x4, int, int>(
"Hash Matrix",
[](float4x4 a, int seed) { return noise::hash(noise::hash_float(a), seed); },
exec_preset);
switch (socket_type) {
case SOCK_MATRIX:
return &fn_hash_matrix;
case SOCK_ROTATION:
return &fn_hash_rotation;
case SOCK_STRING:
return &fn_hash_string;
case SOCK_FLOAT:
return &fn_hash_float;
case SOCK_VECTOR:
return &fn_hash_vector;
case SOCK_RGBA:
return &fn_hash_color;
case SOCK_INT:
return &fn_hash_int;
default:
BLI_assert_unreachable();
return nullptr;
}
}
class SocketSearchOp {
public:
UString socket_name;
eNodeSocketDatatype socket_type;
void operator()(LinkSearchOpParams &params)
{
bNode &node = params.add_node("FunctionNodeHashValue"_ustr);
node.custom1 = socket_type;
params.update_and_connect_available_socket(node, socket_name);
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const mf::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
eNodeSocketDatatype socket_type = params.other_socket().type;
if (!ELEM(socket_type,
SOCK_BOOLEAN,
SOCK_FLOAT,
SOCK_INT,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_VECTOR,
SOCK_STRING,
SOCK_RGBA))
{
return;
}
if (params.in_out() == SOCK_IN) {
if (socket_type == SOCK_BOOLEAN) {
socket_type = SOCK_INT;
}
params.add_item(IFACE_("Value"), SocketSearchOp{"Value"_ustr, socket_type});
params.add_item(IFACE_("Seed"), SocketSearchOp{"Seed"_ustr, SOCK_INT});
}
else {
if (!ELEM(socket_type, SOCK_STRING)) {
const int weight = ELEM(params.other_socket().type, SOCK_INT) ? 0 : -1;
params.add_item(IFACE_("Hash"), SocketSearchOp{"Hash"_ustr, SOCK_INT}, weight);
}
}
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(
srna,
"data_type",
"Data Type",
"",
rna_enum_node_socket_data_type_items,
NOD_inline_enum_accessors(custom1),
SOCK_INT,
[](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
*r_free = true;
return enum_items_filter(rna_enum_node_socket_data_type_items,
[](const EnumPropertyItem &item) -> bool {
return ELEM(item.value,
SOCK_FLOAT,
SOCK_INT,
SOCK_MATRIX,
SOCK_ROTATION,
SOCK_VECTOR,
SOCK_STRING,
SOCK_RGBA);
});
});
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeHashValue"_ustr, FN_NODE_HASH_VALUE);
ntype.ui_name = "Hash Value";
ntype.ui_description = "Generate a randomized integer using the given input value as a seed";
ntype.enum_name_legacy = "HASH_VALUE";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.build_multi_function = node_build_multi_function;
ntype.draw_buttons = node_layout;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_hash_value_cc

View File

@@ -0,0 +1,83 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
#include "node_shader_util.hh"
#include "NOD_geometry_nodes_gizmos.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_fn_input_bool_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Bool>("Boolean"_ustr).custom_draw([](CustomSocketDrawParams &params) {
params.layout.alignment_set(ui::LayoutAlign::Expand);
ui::Layout &row = params.layout.row(true);
row.prop(
&params.node_ptr, "boolean", ui::ITEM_R_SPLIT_EMPTY_NAME, IFACE_("Boolean"), ICON_NONE);
if (gizmos::value_node_has_gizmo(params.tree, params.node)) {
row.prop(&params.socket_ptr, "pin_gizmo", UI_ITEM_NONE, "", ICON_GIZMO);
}
});
}
static int gpu_shader_bool(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack * /*in*/,
GPUNodeStack *out)
{
NodeInputBool *node_storage = static_cast<NodeInputBool *>(node->storage);
float value = float(node_storage->boolean);
return GPU_link(mat, "set_value", GPU_uniform(&value), &out->link);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const bNode &bnode = builder.node();
NodeInputBool *node_storage = static_cast<NodeInputBool *>(bnode.storage);
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<bool>>(node_storage->boolean);
}
NODE_SHADER_MATERIALX_BEGIN
#ifdef WITH_MATERIALX
{
NodeItem boolean = get_output_default("Boolean", NodeItem::Type::Boolean);
return create_node("constant", NodeItem::Type::Boolean, {{"value", boolean}});
}
#endif
NODE_SHADER_MATERIALX_END
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeInputBool *data = MEM_new<NodeInputBool>(__func__);
node->storage = data;
}
static void node_register()
{
static bke::bNodeType ntype;
common_node_type_base(&ntype, "FunctionNodeInputBool"_ustr, FN_NODE_INPUT_BOOL);
ntype.ui_name = "Boolean";
ntype.ui_description =
"Provide a True/False value that can be connected to other nodes in the tree";
ntype.enum_name_legacy = "INPUT_BOOL";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.gpu_fn = gpu_shader_bool;
bke::node_type_storage(
ntype, "NodeInputBool", node_free_standard_storage, node_copy_standard_storage);
ntype.build_multi_function = node_build_multi_function;
ntype.materialx_fn = node_shader_materialx;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_input_bool_cc

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
#include "BLI_math_vector.h"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_fn_input_color_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Color>("Color"_ustr).custom_draw([](CustomSocketDrawParams &params) {
params.layout.alignment_set(ui::LayoutAlign::Expand);
ui::Layout &col = params.layout.column(false);
template_color_picker(&col, &params.node_ptr, "value", true, false, false, false);
col.prop(&params.node_ptr, "value", ui::ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
});
}
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
{
const bNode &bnode = builder.node();
NodeInputColor *node_storage = static_cast<NodeInputColor *>(bnode.storage);
ColorGeometry4f color = (ColorGeometry4f)node_storage->color;
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<ColorGeometry4f>>(color);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeInputColor *data = MEM_new<NodeInputColor>(__func__);
copy_v4_fl4(data->color, 0.5f, 0.5f, 0.5f, 1.0f);
node->storage = data;
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeInputColor"_ustr, FN_NODE_INPUT_COLOR);
ntype.ui_name = "Color";
ntype.ui_description = "Output a color value chosen with the color picker widget";
ntype.enum_name_legacy = "INPUT_COLOR";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.initfunc = node_init;
bke::node_type_storage(
ntype, "NodeInputColor", node_free_standard_storage, node_copy_standard_storage);
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_input_color_cc

View File

@@ -0,0 +1,83 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
#include "node_shader_util.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_geometry_nodes_gizmos.hh"
namespace blender::nodes::node_fn_input_int_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Int>("Integer"_ustr).custom_draw([](CustomSocketDrawParams &params) {
params.layout.alignment_set(ui::LayoutAlign::Expand);
ui::Layout &row = params.layout.row(true);
row.prop(&params.node_ptr, "integer", ui::ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
if (gizmos::value_node_has_gizmo(params.tree, params.node)) {
row.prop(&params.socket_ptr, "pin_gizmo", UI_ITEM_NONE, "", ICON_GIZMO);
}
});
;
}
static int gpu_shader_int(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack * /*in*/,
GPUNodeStack *out)
{
NodeInputInt *node_storage = static_cast<NodeInputInt *>(node->storage);
float integer = float(node_storage->integer);
return GPU_link(mat, "set_value", GPU_uniform(&integer), &out->link);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const bNode &bnode = builder.node();
NodeInputInt *node_storage = static_cast<NodeInputInt *>(bnode.storage);
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<int>>(node_storage->integer);
}
NODE_SHADER_MATERIALX_BEGIN
#ifdef WITH_MATERIALX
{
NodeItem integer = get_output_default("Integer", NodeItem::Type::Integer);
return create_node("constant", NodeItem::Type::Integer, {{"value", integer}});
}
#endif
NODE_SHADER_MATERIALX_END
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeInputInt *data = MEM_new<NodeInputInt>(__func__);
node->storage = data;
}
static void node_register()
{
static bke::bNodeType ntype;
common_node_type_base(&ntype, "FunctionNodeInputInt"_ustr, FN_NODE_INPUT_INT);
ntype.ui_name = "Integer";
ntype.ui_description =
"Provide an integer value that can be connected to other nodes in the tree";
ntype.enum_name_legacy = "INPUT_INT";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.gpu_fn = gpu_shader_int;
bke::node_type_storage(
ntype, "NodeInputInt", node_free_standard_storage, node_copy_standard_storage);
ntype.build_multi_function = node_build_multi_function;
ntype.materialx_fn = node_shader_materialx;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_input_int_cc

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
#include "node_shader_util.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_fn_input_int_vector_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
int dimensions = 3;
if (const bNode *node = b.node_or_null()) {
const auto &storage = *static_cast<NodeInputIntVector *>(node->storage);
dimensions = storage.dimensions;
}
b.add_output<decl::IntVector>("Vector"_ustr)
.dimensions(dimensions)
.custom_draw([](CustomSocketDrawParams &params) {
params.layout.alignment_set(ui::LayoutAlign::Expand);
ui::Layout &row = params.layout.row(true);
row.column(true).prop(
&params.node_ptr, "vector", ui::ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
});
}
static int node_gpu(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack * /*in*/,
GPUNodeStack *out)
{
NodeInputIntVector *node_storage = static_cast<NodeInputIntVector *>(node->storage);
/* Passed as float3 for now since GPU material graphs do not support integer vectors. */
const float3 vector = float3(int3(node_storage->vector));
return GPU_link(mat, "set_rgb", GPU_uniform(vector), &out->link);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const bNode &bnode = builder.node();
NodeInputIntVector *node_storage = static_cast<NodeInputIntVector *>(bnode.storage);
switch (node_storage->dimensions) {
case 2: {
int2 vector(node_storage->vector);
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<int2>>(vector);
break;
}
case 3: {
int3 vector(node_storage->vector);
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<int3>>(vector);
break;
}
default:
BLI_assert_unreachable();
break;
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeInputIntVector *data = MEM_new<NodeInputIntVector>(__func__);
node->storage = data;
}
static void node_layout_ex(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
layout.prop(ptr, "vector_dimensions", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeInputIntVector"_ustr);
ntype.ui_name = "Integer Vector";
ntype.ui_description =
"Provide an integer vector value that can be connected to other nodes in the tree";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.gpu_fn = node_gpu;
ntype.draw_buttons_ex = node_layout_ex;
bke::node_type_storage(
ntype, "NodeInputIntVector", node_free_standard_storage, node_copy_standard_storage);
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_input_int_vector_cc

View File

@@ -0,0 +1,75 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
#include "node_shader_util.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "BKE_node_runtime.hh"
namespace blender::nodes::node_fn_input_menu_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Menu>("Menu"_ustr).custom_draw([](CustomSocketDrawParams &params) {
params.layout.alignment_set(ui::LayoutAlign::Expand);
ui::Layout &row = params.layout.row(true);
const bNodeSocketValueMenu *default_value =
params.node.output_socket(0).default_value_typed<bNodeSocketValueMenu>();
BLI_assert(default_value);
if (default_value->enum_items) {
if (default_value->enum_items->items.is_empty()) {
row.label(IFACE_("No Items"), ICON_NONE);
}
else {
row.prop(&params.node_ptr, "value", UI_ITEM_NONE, "", ICON_NONE);
}
return;
}
if (default_value->has_conflict()) {
row.label(IFACE_("Menu Error"), ICON_ERROR);
}
else {
row.label(IFACE_("Menu Undefined"), ICON_QUESTION);
}
});
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const bNode &bnode = builder.node();
const NodeInputMenu &node_storage = *static_cast<const NodeInputMenu *>(bnode.storage);
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<MenuValue>>(
MenuValue(node_storage.value));
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeInputMenu *data = MEM_new<NodeInputMenu>(__func__);
node->storage = data;
}
static void node_register()
{
static bke::bNodeType ntype;
common_node_type_base(&ntype, "FunctionNodeInputMenu"_ustr);
ntype.ui_name = "Menu";
ntype.ui_description = "Provide a menu value that can be connected to other nodes in the tree";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.initfunc = node_init;
bke::node_type_storage(
ntype, "NodeInputMenu", node_free_standard_storage, node_copy_standard_storage);
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_input_menu_cc

View File

@@ -0,0 +1,65 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_euler.hh"
#include "NOD_geometry_nodes_gizmos.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_input_rotation_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Rotation>("Rotation"_ustr).custom_draw([](CustomSocketDrawParams &params) {
params.layout.alignment_set(ui::LayoutAlign::Expand);
ui::Layout &row = params.layout.row(true);
row.column(true).prop(
&params.node_ptr, "rotation_euler", ui::ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
if (gizmos::value_node_has_gizmo(params.tree, params.node)) {
row.prop(&params.socket_ptr, "pin_gizmo", UI_ITEM_NONE, "", ICON_GIZMO);
}
});
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const bNode &bnode = builder.node();
const NodeInputRotation &node_storage = *static_cast<const NodeInputRotation *>(bnode.storage);
const math::EulerXYZ euler_rotation(node_storage.rotation_euler[0],
node_storage.rotation_euler[1],
node_storage.rotation_euler[2]);
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<math::Quaternion>>(
math::to_quaternion(euler_rotation));
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeInputRotation *data = MEM_new<NodeInputRotation>(__func__);
node->storage = data;
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeInputRotation"_ustr, FN_NODE_INPUT_ROTATION);
ntype.ui_name = "Rotation";
ntype.ui_description =
"Provide a rotation value that can be connected to other nodes in the tree";
ntype.enum_name_legacy = "INPUT_ROTATION";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.initfunc = node_init;
bke::node_type_storage(
ntype, "NodeInputRotation", node_free_standard_storage, node_copy_standard_storage);
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_input_rotation_cc

View File

@@ -0,0 +1,64 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
namespace blender::nodes::node_fn_input_special_characters_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::String>("Line Break"_ustr);
b.add_output<decl::String>("Tab"_ustr).translation_context(BLT_I18NCONTEXT_ID_TEXT);
}
class MF_SpecialCharacters : public mf::MultiFunction {
public:
MF_SpecialCharacters()
{
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Special Characters", signature};
builder.single_output<std::string>("Line Break");
builder.single_output<std::string>("Tab");
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
MutableSpan<std::string> lb = params.uninitialized_single_output<std::string>(0, "Line Break");
MutableSpan<std::string> tab = params.uninitialized_single_output<std::string>(1, "Tab");
mask.foreach_index([&](const int64_t i) {
new (&lb[i]) std::string("\n");
new (&tab[i]) std::string("\t");
});
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static MF_SpecialCharacters special_characters_fn;
builder.set_matching_fn(special_characters_fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(
&ntype, "FunctionNodeInputSpecialCharacters"_ustr, FN_NODE_INPUT_SPECIAL_CHARACTERS);
ntype.ui_name = "Special Characters";
ntype.ui_description =
"Output string characters that cannot be typed directly with the keyboard";
ntype.enum_name_legacy = "INPUT_SPECIAL_CHARACTERS";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_input_special_characters_cc

View File

@@ -0,0 +1,127 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
#include "NOD_socket_search_link.hh"
#include "BLT_translation.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "BLO_read_write.hh"
#include "BLF_api.hh"
namespace blender::nodes::node_fn_input_string_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::String>("String"_ustr).custom_draw([](CustomSocketDrawParams &params) {
params.layout.alignment_set(ui::LayoutAlign::Expand);
params.layout.textbox_with_state(
&params.node_ptr,
"string",
RNA_pointer_get(&params.node_ptr, "textbox_state").data_as<TextboxState>(),
IFACE_("String"));
});
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const bNode &bnode = builder.node();
NodeInputString *node_storage = static_cast<NodeInputString *>(bnode.storage);
std::string string = std::string((node_storage->string) ? node_storage->string : "");
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<std::string>>(std::move(string));
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeInputString *storage = MEM_new<NodeInputString>(__func__);
storage->textbox_state.visible_lines = 1;
node->storage = storage;
}
static void node_storage_free(bNode *node)
{
NodeInputString *storage = static_cast<NodeInputString *>(node->storage);
if (storage == nullptr) {
return;
}
if (storage->string != nullptr) {
MEM_delete(storage->string);
}
MEM_delete(storage);
}
static void node_storage_copy(bNodeTree * /*dst_ntree*/, bNode *dest_node, const bNode *src_node)
{
NodeInputString *source_storage = static_cast<NodeInputString *>(src_node->storage);
NodeInputString *destination_storage = static_cast<NodeInputString *>(
MEM_dupalloc(source_storage));
if (source_storage->string) {
destination_storage->string = MEM_dupalloc(source_storage->string);
}
dest_node->storage = destination_storage;
}
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
{
const NodeInputString *storage = static_cast<const NodeInputString *>(node.storage);
writer.write_string(storage->string);
}
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
{
NodeInputString *storage = static_cast<NodeInputString *>(node.storage);
BLO_read_string(&reader, &storage->string);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const eNodeSocketDatatype type = params.other_socket().type;
if (type != SOCK_STRING) {
return;
}
if (params.other_socket().in_out == SOCK_OUT) {
return;
}
params.add_item(IFACE_("String"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeInputString"_ustr);
params.update_and_connect_available_socket(node, "String"_ustr);
/* Adapt width of the new node to its content. */
const StringRef string = static_cast<NodeInputString *>(node.storage)->string;
const uiFontStyle &fstyle = ui::style_get()->widget;
BLF_size(fstyle.uifont_id, fstyle.points);
const float width = BLF_width(fstyle.uifont_id, string.data(), string.size()) + 40.0f;
node.width = std::clamp(width, 140.0f, 1000.0f);
});
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeInputString"_ustr, FN_NODE_INPUT_STRING);
ntype.ui_name = "String";
ntype.ui_description = "Provide a string value that can be connected to other nodes in the tree";
ntype.enum_name_legacy = "INPUT_STRING";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.initfunc = node_init;
bke::node_type_storage(ntype, "NodeInputString", node_storage_free, node_storage_copy);
ntype.build_multi_function = node_build_multi_function;
ntype.blend_write_storage_content = node_blend_write;
ntype.blend_data_read_storage_content = node_blend_read;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_input_string_cc

View File

@@ -0,0 +1,112 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
#include "node_shader_util.hh"
#include "NOD_geometry_nodes_gizmos.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_fn_input_vector_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
int dimensions = 3;
if (const bNode *node = b.node_or_null()) {
const auto &storage = *static_cast<NodeInputVector *>(node->storage);
dimensions = storage.dimensions;
}
b.add_output<decl::Vector>("Vector"_ustr)
.dimensions(dimensions)
.custom_draw([](CustomSocketDrawParams &params) {
params.layout.alignment_set(ui::LayoutAlign::Expand);
ui::Layout &row = params.layout.row(true);
row.column(true).prop(
&params.node_ptr, "vector", ui::ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
if (gizmos::value_node_has_gizmo(params.tree, params.node)) {
row.prop(&params.socket_ptr, "pin_gizmo", UI_ITEM_NONE, "", ICON_GIZMO);
}
});
}
static int gpu_shader_vector(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack * /*in*/,
GPUNodeStack *out)
{
NodeInputVector *node_storage = static_cast<NodeInputVector *>(node->storage);
return GPU_link(mat, "set_rgb", GPU_uniform(node_storage->vector), &out->link);
}
NODE_SHADER_MATERIALX_BEGIN
#ifdef WITH_MATERIALX
{
NodeItem vector = get_output_default("Vector", NodeItem::Type::Vector3);
return create_node("constant", NodeItem::Type::Vector3, {{"value", vector}});
}
#endif
NODE_SHADER_MATERIALX_END
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const bNode &bnode = builder.node();
NodeInputVector *node_storage = static_cast<NodeInputVector *>(bnode.storage);
switch (node_storage->dimensions) {
case 2: {
float2 vector(node_storage->vector);
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<float2>>(vector);
break;
}
case 3: {
float3 vector(node_storage->vector);
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<float3>>(vector);
break;
}
case 4: {
float4 vector(node_storage->vector);
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<float4>>(vector);
break;
}
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeInputVector *data = MEM_new<NodeInputVector>(__func__);
node->storage = data;
}
static void node_layout_ex(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
layout.prop(ptr, "vector_dimensions", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
static void node_register()
{
static bke::bNodeType ntype;
common_node_type_base(&ntype, "FunctionNodeInputVector"_ustr, FN_NODE_INPUT_VECTOR);
ntype.ui_name = "Vector";
ntype.ui_description = "Provide a vector value that can be connected to other nodes in the tree";
ntype.enum_name_legacy = "INPUT_VECTOR";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.gpu_fn = gpu_shader_vector;
ntype.draw_buttons_ex = node_layout_ex;
bke::node_type_storage(
ntype, "NodeInputVector", node_free_standard_storage, node_copy_standard_storage);
ntype.build_multi_function = node_build_multi_function;
ntype.materialx_fn = node_shader_materialx;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_input_vector_cc

View File

@@ -0,0 +1,308 @@
/* SPDX-FileCopyrightText: 2024 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string.h"
#include "FN_multi_function_registry.hh"
#include "RNA_enum_types.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_inverse_eval_params.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "NOD_value_elem_eval.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_integer_math_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Int>("Value"_ustr).label_fn([](const bNode &node) {
switch (node.custom1) {
case NODE_INTEGER_MATH_POWER:
return IFACE_("Base");
default:
return IFACE_("Value");
}
});
b.add_input<decl::Int>("Value"_ustr, "Value_001"_ustr).label_fn([](const bNode &node) {
switch (node.custom1) {
case NODE_INTEGER_MATH_MULTIPLY_ADD:
return IFACE_("Multiplier");
case NODE_INTEGER_MATH_POWER:
return IFACE_("Exponent");
default:
return IFACE_("Value");
}
});
b.add_input<decl::Int>("Value"_ustr, "Value_002"_ustr).label_fn([](const bNode &node) {
switch (node.custom1) {
case NODE_INTEGER_MATH_MULTIPLY_ADD:
return IFACE_("Addend");
default:
return IFACE_("Value");
}
});
b.add_output<decl::Int>("Value"_ustr);
};
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_update(bNodeTree *ntree, bNode *node)
{
const bool one_input_ops = ELEM(
node->custom1, NODE_INTEGER_MATH_ABSOLUTE, NODE_INTEGER_MATH_SIGN, NODE_INTEGER_MATH_NEGATE);
const bool three_input_ops = ELEM(node->custom1, NODE_INTEGER_MATH_MULTIPLY_ADD);
bNodeSocket *sockA = static_cast<bNodeSocket *>(node->inputs.first);
bNodeSocket *sockB = sockA->next;
bNodeSocket *sockC = sockB->next;
bke::node_set_socket_availability(*ntree, *sockB, !one_input_ops);
bke::node_set_socket_availability(*ntree, *sockC, three_input_ops);
}
static void int_math_input_defaults(bNode &node, const NodeIntegerMathOperation operation)
{
bNodeSocket *socket_2 = bke::node_find_socket(node, SOCK_IN, "Value_001"_ustr);
BLI_assert(socket_2 != nullptr);
int &value_2 = socket_2->default_value_typed<bNodeSocketValueInt>()->value;
switch (operation) {
case NODE_INTEGER_MATH_MULTIPLY:
case NODE_INTEGER_MATH_DIVIDE:
case NODE_INTEGER_MATH_MULTIPLY_ADD:
case NODE_INTEGER_MATH_DIVIDE_CEIL:
case NODE_INTEGER_MATH_DIVIDE_FLOOR:
case NODE_INTEGER_MATH_DIVIDE_ROUND:
case NODE_INTEGER_MATH_FLOORED_MODULO:
case NODE_INTEGER_MATH_MODULO: {
value_2 = 1;
break;
}
default:
/* Use the default defined in the node declaration otherwise. */
break;
}
}
class SocketSearchOp {
public:
UString socket_name;
NodeIntegerMathOperation operation;
void operator()(LinkSearchOpParams &params)
{
bNode &node = params.add_node("FunctionNodeIntegerMath"_ustr);
node.custom1 = NodeIntegerMathOperation(operation);
int_math_input_defaults(node, operation);
params.update_and_connect_available_socket(node, socket_name);
}
};
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
if (!params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_INT)) {
return;
}
const bool is_integer = params.other_socket().type == SOCK_INT;
const int weight = is_integer ? 0 : -1;
/* Add socket A operations. */
for (const EnumPropertyItem *item = rna_enum_node_integer_math_items;
item->identifier != nullptr;
item++)
{
if (item->name != nullptr && item->identifier[0] != '\0') {
params.add_item(CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, item->name),
SocketSearchOp{"Value"_ustr, NodeIntegerMathOperation(item->value)},
weight);
}
}
}
static void node_label(const bNodeTree * /*ntree*/,
const bNode *node,
char *label,
int label_maxncpy)
{
const char *name;
bool enum_label = RNA_enum_name(rna_enum_node_integer_math_items, node->custom1, &name);
if (!enum_label) {
name = CTX_N_(BLT_I18NCONTEXT_ID_NODETREE, "Unknown");
}
BLI_strncpy(label, CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, name), label_maxncpy);
}
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
{
switch (NodeIntegerMathOperation(bnode.custom1)) {
case NODE_INTEGER_MATH_ADD:
return &fn::multi_function::registry::lookup("int + int"_ustr);
case NODE_INTEGER_MATH_SUBTRACT:
return &fn::multi_function::registry::lookup("int - int"_ustr);
case NODE_INTEGER_MATH_MULTIPLY:
return &fn::multi_function::registry::lookup("int * int"_ustr);
case NODE_INTEGER_MATH_DIVIDE:
return &fn::multi_function::registry::lookup("int / int"_ustr);
case NODE_INTEGER_MATH_DIVIDE_FLOOR:
return &fn::multi_function::registry::lookup("floor(int, int)"_ustr);
case NODE_INTEGER_MATH_DIVIDE_CEIL:
return &fn::multi_function::registry::lookup("divide_ceil(int, int)"_ustr);
case NODE_INTEGER_MATH_DIVIDE_ROUND:
return &fn::multi_function::registry::lookup("divide_round(int, int)"_ustr);
case NODE_INTEGER_MATH_POWER:
return &fn::multi_function::registry::lookup("int ** int"_ustr);
case NODE_INTEGER_MATH_MULTIPLY_ADD:
return &fn::multi_function::registry::lookup("int * int + int"_ustr);
case NODE_INTEGER_MATH_FLOORED_MODULO:
return &fn::multi_function::registry::lookup("mod_periodic(int, int)"_ustr);
case NODE_INTEGER_MATH_MODULO:
return &fn::multi_function::registry::lookup("int % int"_ustr);
case NODE_INTEGER_MATH_ABSOLUTE:
return &fn::multi_function::registry::lookup("abs(int)"_ustr);
case NODE_INTEGER_MATH_SIGN:
return &fn::multi_function::registry::lookup("sign(int)"_ustr);
case NODE_INTEGER_MATH_MINIMUM:
return &fn::multi_function::registry::lookup("min(int, int)"_ustr);
case NODE_INTEGER_MATH_MAXIMUM:
return &fn::multi_function::registry::lookup("max(int, int)"_ustr);
case NODE_INTEGER_MATH_GCD:
return &fn::multi_function::registry::lookup("gcd(int, int)"_ustr);
case NODE_INTEGER_MATH_LCM:
return &fn::multi_function::registry::lookup("lcm(int, int)"_ustr);
case NODE_INTEGER_MATH_NEGATE:
return &fn::multi_function::registry::lookup("-int"_ustr);
}
BLI_assert_unreachable();
return nullptr;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const mf::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
const NodeIntegerMathOperation op = NodeIntegerMathOperation(params.node.custom1);
switch (op) {
case NODE_INTEGER_MATH_ADD:
case NODE_INTEGER_MATH_SUBTRACT:
case NODE_INTEGER_MATH_MULTIPLY:
case NODE_INTEGER_MATH_DIVIDE: {
IntElem output_elem = params.get_input_elem<IntElem>("Value"_ustr);
output_elem.merge(params.get_input_elem<IntElem>("Value_001"_ustr));
params.set_output_elem("Value"_ustr, output_elem);
break;
}
default:
break;
}
}
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams &params)
{
const NodeIntegerMathOperation op = NodeIntegerMathOperation(params.node.custom1);
switch (op) {
case NODE_INTEGER_MATH_ADD:
case NODE_INTEGER_MATH_SUBTRACT:
case NODE_INTEGER_MATH_MULTIPLY:
case NODE_INTEGER_MATH_DIVIDE: {
params.set_input_elem("Value"_ustr,
params.get_output_elem<value_elem::IntElem>("Value"_ustr));
break;
}
default:
break;
}
}
static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
{
const NodeIntegerMathOperation op = NodeIntegerMathOperation(params.node.custom1);
const UString first_input_id = "Value"_ustr;
const UString second_input_id = "Value_001"_ustr;
const UString output_id = "Value"_ustr;
switch (op) {
case NODE_INTEGER_MATH_ADD: {
params.set_input(first_input_id,
params.get_output<int>(output_id) - params.get_input<int>(second_input_id));
break;
}
case NODE_INTEGER_MATH_SUBTRACT: {
params.set_input(first_input_id,
params.get_output<int>(output_id) + params.get_input<int>(second_input_id));
break;
}
case NODE_INTEGER_MATH_MULTIPLY: {
params.set_input(first_input_id,
math::safe_divide(params.get_output<int>(output_id),
params.get_input<int>(second_input_id)));
break;
}
case NODE_INTEGER_MATH_DIVIDE: {
params.set_input(first_input_id,
params.get_output<int>(output_id) * params.get_input<int>(second_input_id));
break;
}
default: {
break;
}
}
}
static void node_rna(StructRNA *srna)
{
PropertyRNA *prop;
prop = RNA_def_node_enum(srna,
"operation",
"Operation",
"",
rna_enum_node_integer_math_items,
NOD_inline_enum_accessors(custom1),
NODE_INTEGER_MATH_ADD);
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_NODETREE);
RNA_def_property_update_runtime(prop, rna_Node_socket_update);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeIntegerMath"_ustr, FN_NODE_INTEGER_MATH);
ntype.ui_name = "Integer Math";
ntype.ui_description = "Perform various math operations on the given integer inputs";
ntype.enum_name_legacy = "INTEGER_MATH";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.labelfunc = node_label;
ntype.updatefunc = node_update;
ntype.build_multi_function = node_build_multi_function;
ntype.draw_buttons = node_layout;
ntype.gather_link_search_ops = node_gather_link_searches;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;
ntype.eval_inverse = node_eval_inverse;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_integer_math_cc

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "GPU_material.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_invert_matrix_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.is_function_node();
b.add_input<decl::Matrix>("Matrix"_ustr);
b.add_output<decl::Matrix>("Matrix"_ustr)
.description("The inverted matrix or the identity matrix if the input is not invertible")
.align_with_previous();
b.add_output<decl::Bool>("Invertible"_ustr)
.description("True if the input matrix is invertible");
}
class InvertMatrixFunction : public mf::MultiFunction {
public:
InvertMatrixFunction()
{
static mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Invert Matrix", signature};
builder.single_input<float4x4>("Matrix");
builder.single_output<float4x4>("Matrix", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<bool>("Invertible", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArraySpan<float4x4> in_matrices = params.readonly_single_input<float4x4>(0, "Matrix");
MutableSpan<float4x4> out_matrices = params.uninitialized_single_output_if_required<float4x4>(
1, "Matrix");
MutableSpan<bool> out_invertible = params.uninitialized_single_output_if_required<bool>(
2, "Invertible");
mask.foreach_index([&](const int64_t i) {
const float4x4 &matrix = in_matrices[i];
bool success;
float4x4 inverted_matrix = math::invert(matrix, success);
if (!out_matrices.is_empty()) {
out_matrices[i] = success ? inverted_matrix : float4x4::identity();
}
if (!out_invertible.is_empty()) {
out_invertible[i] = success;
}
});
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static InvertMatrixFunction fn;
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *material,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *inputs,
GPUNodeStack *outputs)
{
return GPU_stack_link(material, node, "node_function_invert_matrix", inputs, outputs);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeInvertMatrix"_ustr, FN_NODE_INVERT_MATRIX);
ntype.ui_name = "Invert Matrix";
ntype.ui_description = "Compute the inverse of the given matrix, if one exists";
ntype.enum_name_legacy = "INVERT_MATRIX";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.gpu_fn = node_gpu_material;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_invert_matrix_cc

View File

@@ -0,0 +1,52 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_quaternion.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_invert_rotation_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.is_function_node();
b.add_input<decl::Rotation>("Rotation"_ustr);
b.add_output<decl::Rotation>("Rotation"_ustr).align_with_previous();
};
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "invert_rotation", in, out);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI1_SO<math::Quaternion, math::Quaternion>(
"Invert Quaternion", [](math::Quaternion quat) { return math::invert(quat); });
builder.set_matching_fn(fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeInvertRotation"_ustr, FN_NODE_INVERT_ROTATION);
ntype.ui_name = "Invert Rotation";
ntype.ui_description = "Compute the inverse of the given rotation";
ntype.enum_name_legacy = "INVERT_ROTATION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_invert_rotation_cc

View File

@@ -0,0 +1,132 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string_ref.hh"
#include "BLI_string_utf8.h"
#include "BKE_node_runtime.hh"
#include "node_function_util.hh"
#include "NOD_socket_search_link.hh"
namespace blender::nodes::node_fn_match_string_cc {
enum class MatchStringOperation : int8_t { StartsWith, EndsWith, Contains };
const EnumPropertyItem rna_enum_node_match_string_items[] = {
{int(MatchStringOperation::StartsWith),
"STARTS_WITH",
0,
N_("Starts With"),
N_("True when the first input starts with the second")},
{int(MatchStringOperation::EndsWith),
"ENDS_WITH",
0,
N_("Ends With"),
N_("True when the first input ends with the second")},
{int(MatchStringOperation::Contains),
"CONTAINS",
0,
N_("Contains"),
N_("True when the first input contains the second as a substring")},
{0, nullptr, 0, nullptr, nullptr},
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::String>("String"_ustr).optional_label().is_default_link_socket();
b.add_input<decl::Menu>("Operation"_ustr)
.static_items(rna_enum_node_match_string_items)
.optional_label();
b.add_input<decl::String>("Key"_ustr)
.optional_label()
.description("The string to find in the input string");
b.add_output<decl::Bool>("Result"_ustr);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI3_SO<std::string, int, std::string, bool>(
"Starts With", [](const std::string &a, const int mode, const std::string &b) {
const StringRef strref_a(a);
const StringRef strref_b(b);
switch (MatchStringOperation(mode)) {
case MatchStringOperation::StartsWith: {
return strref_a.startswith(strref_b);
}
case MatchStringOperation::EndsWith: {
return strref_a.endswith(strref_b);
}
case MatchStringOperation::Contains: {
return strref_a.find(strref_b) != StringRef::not_found;
}
}
return false;
});
builder.set_matching_fn(&fn);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
if (params.in_out() == SOCK_IN) {
if (params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_STRING)) {
for (const EnumPropertyItem *item = rna_enum_node_match_string_items;
item->identifier != nullptr;
item++)
{
if (item->name != nullptr && item->identifier[0] != '\0') {
MatchStringOperation operation = MatchStringOperation(item->value);
params.add_item(IFACE_(item->name), [operation](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeMatchString"_ustr);
params.update_and_connect_available_socket(node, "String"_ustr);
bke::node_find_socket(node, SOCK_IN, "Operation"_ustr)
->default_value_typed<bNodeSocketValueMenu>()
->value = int(operation);
});
}
}
}
}
else {
params.add_item(IFACE_("Result"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeMatchString"_ustr);
params.update_and_connect_available_socket(node, "Result"_ustr);
});
}
}
static void node_label(const bNodeTree * /*tree*/,
const bNode *node,
char *label,
int label_maxncpy)
{
const char *name;
bool enum_label = RNA_enum_name(rna_enum_node_match_string_items, node->custom1, &name);
if (!enum_label) {
name = N_("Unknown");
}
BLI_strncpy_utf8(label, IFACE_(name), label_maxncpy);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeMatchString"_ustr);
ntype.ui_name = "Match String";
ntype.ui_description = "Check if a given string exists within another string";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.labelfunc = node_label;
ntype.gather_link_search_ops = node_gather_link_searches;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_match_string_cc

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "GPU_material.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_matrix_determinant_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Matrix>("Matrix"_ustr);
b.add_output<decl::Float>("Determinant"_ustr);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI1_SO<float4x4, float>(
"Matrix Determinant", [](const float4x4 &matrix) { return math::determinant(matrix); });
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *material,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *inputs,
GPUNodeStack *outputs)
{
return GPU_stack_link(material, node, "node_function_matrix_determinant", inputs, outputs);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeMatrixDeterminant"_ustr, FN_NODE_MATRIX_DETERMINANT);
ntype.ui_name = "Matrix Determinant";
ntype.ui_description = "Compute the determinant of the given matrix";
ntype.enum_name_legacy = "MATRIX_DETERMINANT";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.gpu_fn = node_gpu_material;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_matrix_determinant_cc

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "NOD_inverse_eval_params.hh"
#include "NOD_value_elem_eval.hh"
#include "GPU_material.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_matrix_multiply_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Matrix>("Matrix"_ustr);
b.add_input<decl::Matrix>("Matrix"_ustr, "Matrix_001"_ustr);
b.add_output<decl::Matrix>("Matrix"_ustr);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI2_SO<float4x4, float4x4, float4x4>(
"Multiply Matrices", [](float4x4 a, float4x4 b) { return a * b; });
builder.set_matching_fn(fn);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
params.set_output_elem("Matrix"_ustr, MatrixElem::all());
}
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams &params)
{
using namespace value_elem;
const MatrixElem first_input_elem = MatrixElem::all();
params.set_input_elem("Matrix"_ustr, first_input_elem);
}
static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
{
const float4x4 output = params.get_output<float4x4>("Matrix"_ustr);
const float4x4 second_input = params.get_input<float4x4>("Matrix_001"_ustr);
const float4x4 first_input = output * math::invert(second_input);
params.set_input("Matrix"_ustr, first_input);
}
static int node_gpu_material(GPUMaterial *material,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *inputs,
GPUNodeStack *outputs)
{
return GPU_stack_link(material, node, "node_function_matrix_multiply", inputs, outputs);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeMatrixMultiply"_ustr, FN_NODE_MATRIX_MULTIPLY);
ntype.ui_name = "Multiply Matrices";
ntype.ui_description = "Perform a matrix multiplication on two input matrices";
ntype.enum_name_legacy = "MATRIX_MULTIPLY";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;
ntype.eval_inverse = node_eval_inverse;
ntype.gpu_fn = node_gpu_material;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_matrix_multiply_cc

View File

@@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_solvers.h"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_matrix_svd_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Matrix>("Matrix"_ustr)
.description("Matrix to decompose, only the 3x3 part is used");
b.add_output<decl::Matrix>("U"_ustr).description("Left singular vectors");
b.add_output<decl::Vector>("S"_ustr).description("Singular values");
b.add_output<decl::Matrix>("V"_ustr).description("Right singular vectors");
}
class MatrixSVDFunction : public mf::MultiFunction {
public:
MatrixSVDFunction()
{
static mf::Signature signature_;
mf::SignatureBuilder builder{"Matrix SVD", signature_};
builder.single_input<float4x4>("Matrix");
builder.single_output<float4x4>("U");
builder.single_output<float3>("S");
builder.single_output<float4x4>("V");
this->set_signature(&signature_);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArraySpan<float4x4> matrices = params.readonly_single_input<float4x4>(0, "Matrix");
MutableSpan<float4x4> Us = params.uninitialized_single_output<float4x4>(1, "U");
MutableSpan<float3> Ss = params.uninitialized_single_output<float3>(2, "S");
MutableSpan<float4x4> Vs = params.uninitialized_single_output<float4x4>(3, "V");
mask.foreach_index([&](const int64_t i) {
const float3x3 matrix = matrices[i].view<3, 3>();
float3x3 matrix_U, matrix_V;
BLI_svd_m3(matrix.ptr(), matrix_U.ptr(), Ss[i], matrix_V.ptr());
Us[i] = float4x4(matrix_U);
Vs[i] = float4x4(matrix_V);
});
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static MatrixSVDFunction fn;
builder.set_matching_fn(fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeMatrixSVD"_ustr);
ntype.ui_name = "Matrix SVD";
ntype.ui_description = "Compute the singular value decomposition of the 3x3 part of a matrix";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_matrix_svd_cc

View File

@@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "GPU_material.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_project_point_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.is_function_node();
b.add_input<decl::Vector>("Vector"_ustr).subtype(PROP_XYZ);
b.add_output<decl::Vector>("Vector"_ustr).subtype(PROP_XYZ).align_with_previous();
b.add_input<decl::Matrix>("Transform"_ustr);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI2_SO<float3, float4x4, float3>(
"Project Point",
[](float3 point, float4x4 matrix) { return math::project_point(matrix, point); });
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *material,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *inputs,
GPUNodeStack *outputs)
{
return GPU_stack_link(material, node, "node_function_project_point", inputs, outputs);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeProjectPoint"_ustr, FN_NODE_PROJECT_POINT);
ntype.ui_name = "Project Point";
ntype.ui_description =
"Project a point using a matrix, using location, rotation, scale, and perspective divide";
ntype.enum_name_legacy = "PROJECT_POINT";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.gpu_fn = node_gpu_material;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_project_point_cc

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_quaternion.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_quaternion_to_rotation_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("W"_ustr).default_value(1.0f);
b.add_input<decl::Float>("X"_ustr).default_value(0.0f);
b.add_input<decl::Float>("Y"_ustr).default_value(0.0f);
b.add_input<decl::Float>("Z"_ustr).default_value(0.0f);
b.add_output<decl::Rotation>("Rotation"_ustr);
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI4_SO<float, float, float, float, math::Quaternion>(
"Quaternion to Rotation", [](float w, float x, float y, float z) {
math::Quaternion combined(w, x, y, z);
return math::normalize(combined);
});
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "quaternion_to_rotation", in, out);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(
&ntype, "FunctionNodeQuaternionToRotation"_ustr, FN_NODE_QUATERNION_TO_ROTATION);
ntype.ui_name = "Quaternion to Rotation";
ntype.ui_description = "Build a rotation from quaternion components";
ntype.enum_name_legacy = "QUATERNION_TO_ROTATION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_quaternion_to_rotation_cc

View File

@@ -0,0 +1,220 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
// #include "BLI_hash.h"
#include "BLI_noise.hh"
#include "node_function_util.hh"
#include "NOD_socket_search_link.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_fn_random_value_cc {
NODE_STORAGE_FUNCS(NodeRandomValue)
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
const bNode *node = b.node_or_null();
if (node != nullptr) {
const NodeRandomValue &storage = node_storage(*node);
const eCustomDataType data_type = eCustomDataType(storage.data_type);
switch (data_type) {
case CD_PROP_FLOAT3:
b.add_input<decl::Vector>("Min"_ustr);
b.add_input<decl::Vector>("Max"_ustr).default_value({1.0f, 1.0f, 1.0f});
break;
case CD_PROP_FLOAT:
b.add_input<decl::Float>("Min"_ustr);
b.add_input<decl::Float>("Max"_ustr).default_value(1.0f);
break;
case CD_PROP_INT32:
b.add_input<decl::Int>("Min"_ustr);
b.add_input<decl::Int>("Max"_ustr).default_value(100);
break;
case CD_PROP_BOOL:
b.add_input<decl::Float>("Probability"_ustr)
.min(0.0f)
.max(1.0f)
.default_value(0.5f)
.subtype(PROP_FACTOR);
break;
default:
BLI_assert_unreachable();
break;
}
}
b.add_input<decl::Int>("ID"_ustr)
.structure_type(StructureType::Dynamic)
.default_input_type(NODE_DEFAULT_INPUT_ID_INDEX_FIELD);
b.add_input<decl::Int>("Seed"_ustr);
if (node != nullptr) {
const NodeRandomValue &storage = node_storage(*node);
const eCustomDataType data_type = eCustomDataType(storage.data_type);
b.add_output(data_type, "Value"_ustr);
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void fn_node_random_value_init(bNodeTree * /*tree*/, bNode *node)
{
NodeRandomValue *data = MEM_new<NodeRandomValue>(__func__);
data->data_type = CD_PROP_FLOAT;
node->storage = data;
}
static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT:
return CD_PROP_FLOAT;
case SOCK_BOOLEAN:
return CD_PROP_BOOL;
case SOCK_INT:
return CD_PROP_INT32;
case SOCK_VECTOR:
case SOCK_RGBA:
case SOCK_ROTATION:
return CD_PROP_FLOAT3;
default:
return {};
}
}
static void node_gather_link_search_ops(GatherLinkSearchOpParams &params)
{
const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
if (!type) {
return;
}
if (params.in_out() == SOCK_IN) {
if (ELEM(*type, CD_PROP_INT32, CD_PROP_FLOAT3, CD_PROP_FLOAT)) {
params.add_item(IFACE_("Min"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeRandomValue"_ustr);
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Min"_ustr);
});
params.add_item(IFACE_("Max"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeRandomValue"_ustr);
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Max"_ustr);
});
}
if (*type == CD_PROP_FLOAT) {
params.add_item(IFACE_("Probability"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeRandomValue"_ustr);
node_storage(node).data_type = CD_PROP_BOOL;
params.update_and_connect_available_socket(node, "Probability"_ustr);
});
}
}
else {
params.add_item(IFACE_("Value"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeRandomValue"_ustr);
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Value"_ustr);
});
}
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const NodeRandomValue &storage = node_storage(builder.node());
const eCustomDataType data_type = eCustomDataType(storage.data_type);
switch (data_type) {
case CD_PROP_FLOAT3: {
static auto fn = mf::build::SI4_SO<float3, float3, int, int, float3>(
"Random Vector",
[](float3 min_value, float3 max_value, int id, int seed) -> float3 {
const float x = noise::hash_to_float(seed, id, 0);
const float y = noise::hash_to_float(seed, id, 1);
const float z = noise::hash_to_float(seed, id, 2);
return float3(x, y, z) * (max_value - min_value) + min_value;
},
mf::build::exec_presets::SomeSpanOrSingle<2>());
builder.set_matching_fn(fn);
break;
}
case CD_PROP_FLOAT: {
static auto fn = mf::build::SI4_SO<float, float, int, int, float>(
"Random Float",
[](float min_value, float max_value, int id, int seed) -> float {
const float value = noise::hash_to_float(seed, id);
return value * (max_value - min_value) + min_value;
},
mf::build::exec_presets::SomeSpanOrSingle<2>());
builder.set_matching_fn(fn);
break;
}
case CD_PROP_INT32: {
static auto fn = mf::build::SI4_SO<int, int, int, int, int>(
"Random Int",
[](int min_value, int max_value, int id, int seed) -> int {
if (min_value > max_value) {
std::swap(min_value, max_value);
}
const uint32_t hash = noise::hash(id, seed);
/* Calculate range using unsigned types to fit the entire 32-bit space. */
const uint32_t range = uint32_t(max_value) - uint32_t(min_value) + 1;
/* Range wraps around to 0 when min_value is INT_MIN and max_value is INT_MAX.
* so the modulo is unnecessary and would cause a division by zero. */
const uint32_t modulo_result = (range == 0) ? hash : (hash % range);
return int(uint32_t(min_value) + modulo_result);
},
mf::build::exec_presets::SomeSpanOrSingle<2>());
builder.set_matching_fn(fn);
break;
}
case CD_PROP_BOOL: {
static auto fn = mf::build::SI3_SO<float, int, int, bool>(
"Random Bool",
[](float probability, int id, int seed) -> bool {
return noise::hash_to_float(id, seed) <= probability;
},
mf::build::exec_presets::SomeSpanOrSingle<1>());
builder.set_matching_fn(fn);
break;
}
default: {
BLI_assert_unreachable();
break;
}
}
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeRandomValue"_ustr, FN_NODE_RANDOM_VALUE);
ntype.ui_name = "Random Value";
ntype.ui_description = "Output a randomized value";
ntype.enum_name_legacy = "RANDOM_VALUE";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.initfunc = fn_node_random_value_init;
ntype.draw_buttons = node_layout;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.gather_link_search_ops = node_gather_link_search_ops;
bke::node_type_storage(
ntype, "NodeRandomValue", node_free_standard_storage, node_copy_standard_storage);
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_random_value_cc

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string_utils.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_replace_string_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::String>("String"_ustr).optional_label();
b.add_output<decl::String>("String"_ustr).align_with_previous();
b.add_input<decl::String>("Find"_ustr).description("The string to find in the input string");
b.add_input<decl::String>("Replace"_ustr).description("The string to replace each match with");
}
static std::string replace_all(const StringRefNull str,
const StringRefNull from,
const StringRefNull to)
{
if (from.is_empty()) {
return str;
}
char *new_str_ptr = BLI_string_replaceN(str.c_str(), from.c_str(), to.c_str());
std::string new_str{new_str_ptr};
MEM_delete(new_str_ptr);
return new_str;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto substring_fn = mf::build::SI3_SO<std::string, std::string, std::string, std::string>(
"Replace", [](const std::string &str, const std::string &find, const std::string &replace) {
return replace_all(str, find, replace);
});
builder.set_matching_fn(&substring_fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeReplaceString"_ustr, FN_NODE_REPLACE_STRING);
ntype.ui_name = "Replace String";
ntype.ui_description = "Replace a given string segment with another";
ntype.enum_name_legacy = "REPLACE_STRING";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_replace_string_cc

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string_utf8.h"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_reverse_string_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::String>("String"_ustr).optional_label();
b.add_output<decl::String>("String"_ustr).align_with_previous();
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto reverse_fn = mf::build::SI1_SO<std::string, std::string>(
"Reverse", [](const std::string &s) {
if (s.empty()) {
return std::string();
}
std::string result;
result.reserve(s.size());
const char *start = s.data();
const char *curr = start + s.size();
while (curr > start) {
const char *prev = BLI_str_find_prev_char_utf8(curr, start);
size_t char_len = curr - prev;
result.append(prev, char_len);
curr = prev;
}
return result;
});
builder.set_matching_fn(&reverse_fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeReverseString"_ustr);
ntype.ui_name = "Reverse String";
ntype.ui_description = "Reverse the order of the characters in a string";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_reverse_string_cc

View File

@@ -0,0 +1,139 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_listbase.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "RNA_enum_types.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_rotate_euler_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Rotation"_ustr).subtype(PROP_EULER).hide_value();
const bNode *node = b.node_or_null();
if (node != nullptr) {
const auto type = FunctionNodeRotateEulerType(node->custom1);
switch (type) {
case FN_NODE_ROTATE_EULER_TYPE_EULER:
b.add_input<decl::Vector>("Rotate By"_ustr).subtype(PROP_EULER);
break;
case FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE: {
b.add_input<decl::Vector>("Axis"_ustr).default_value({0.0, 0.0, 1.0}).subtype(PROP_XYZ);
b.add_input<decl::Float>("Angle"_ustr).subtype(PROP_ANGLE);
break;
}
default:
BLI_assert_unreachable();
break;
}
}
b.add_output<decl::Vector>("Rotation"_ustr);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "rotation_type", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
layout.prop(ptr, "space", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
{
static auto obj_euler_rot = mf::build::SI2_SO<float3, float3, float3>(
"Rotate Euler by Euler/Object", [](const float3 &input, const float3 &rotation) {
float input_mat[3][3];
eul_to_mat3(input_mat, input);
float rot_mat[3][3];
eul_to_mat3(rot_mat, rotation);
float mat_res[3][3];
mul_m3_m3m3(mat_res, rot_mat, input_mat);
float3 result;
mat3_to_eul(result, mat_res);
return result;
});
static auto obj_AA_rot = mf::build::SI3_SO<float3, float3, float, float3>(
"Rotate Euler by AxisAngle/Object",
[](const float3 &input, const float3 &axis, float angle) {
float input_mat[3][3];
eul_to_mat3(input_mat, input);
float rot_mat[3][3];
axis_angle_to_mat3(rot_mat, axis, angle);
float mat_res[3][3];
mul_m3_m3m3(mat_res, rot_mat, input_mat);
float3 result;
mat3_to_eul(result, mat_res);
return result;
});
static auto local_euler_rot = mf::build::SI2_SO<float3, float3, float3>(
"Rotate Euler by Euler/Local", [](const float3 &input, const float3 &rotation) {
float input_mat[3][3];
eul_to_mat3(input_mat, input);
float rot_mat[3][3];
eul_to_mat3(rot_mat, rotation);
float mat_res[3][3];
mul_m3_m3m3(mat_res, input_mat, rot_mat);
float3 result;
mat3_to_eul(result, mat_res);
return result;
});
static auto local_AA_rot = mf::build::SI3_SO<float3, float3, float, float3>(
"Rotate Euler by AxisAngle/Local", [](const float3 &input, const float3 &axis, float angle) {
float input_mat[3][3];
eul_to_mat3(input_mat, input);
float rot_mat[3][3];
axis_angle_to_mat3(rot_mat, axis, angle);
float mat_res[3][3];
mul_m3_m3m3(mat_res, input_mat, rot_mat);
float3 result;
mat3_to_eul(result, mat_res);
return result;
});
short type = bnode.custom1;
short space = bnode.custom2;
if (type == FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE) {
return space == FN_NODE_ROTATE_EULER_SPACE_OBJECT ?
static_cast<const mf::MultiFunction *>(&obj_AA_rot) :
&local_AA_rot;
}
if (type == FN_NODE_ROTATE_EULER_TYPE_EULER) {
return space == FN_NODE_ROTATE_EULER_SPACE_OBJECT ?
static_cast<const mf::MultiFunction *>(&obj_euler_rot) :
&local_euler_rot;
}
BLI_assert_unreachable();
return nullptr;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const mf::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeRotateEuler"_ustr, FN_NODE_ROTATE_EULER);
ntype.ui_name = "Rotate Euler";
ntype.ui_description = "Apply a secondary Euler rotation to a given Euler rotation";
ntype.enum_name_legacy = "ROTATE_EULER";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.build_multi_function = node_build_multi_function;
ntype.deprecation_notice = N_("Use the \"Rotate Rotation\" node instead");
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_rotate_euler_cc

View File

@@ -0,0 +1,118 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_rna_define.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_rotate_rotation_cc {
enum class RotationSpace {
Global = 0,
Local = 1,
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
b.is_function_node();
b.add_input<decl::Rotation>("Rotation"_ustr);
b.add_output<decl::Rotation>("Rotation"_ustr).align_with_previous();
b.add_input<decl::Rotation>("Rotate By"_ustr);
};
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "rotation_space", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
const char *name = nullptr;
switch (RotationSpace(node->custom1)) {
case RotationSpace::Global:
name = "rotate_rotation_global";
break;
case RotationSpace::Local:
name = "rotate_rotation_local";
break;
}
if (name != nullptr) {
return GPU_stack_link(mat, node, name, in, out);
}
return 0;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
switch (RotationSpace(builder.node().custom1)) {
case RotationSpace::Global: {
static auto fn = mf::build::SI2_SO<math::Quaternion, math::Quaternion, math::Quaternion>(
"Rotate Rotation Global", [](math::Quaternion a, math::Quaternion b) { return b * a; });
builder.set_matching_fn(fn);
break;
}
case RotationSpace::Local: {
static auto fn = mf::build::SI2_SO<math::Quaternion, math::Quaternion, math::Quaternion>(
"Rotate Rotation Local", [](math::Quaternion a, math::Quaternion b) { return a * b; });
builder.set_matching_fn(fn);
break;
}
}
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem space_items[] = {
{int(RotationSpace::Global),
"GLOBAL",
ICON_NONE,
"Global",
"Rotate the input rotation in global space"},
{int(RotationSpace::Local),
"LOCAL",
ICON_NONE,
"Local",
"Rotate the input rotation in its local space"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"rotation_space",
"Space",
"Base orientation for the rotation",
space_items,
NOD_inline_enum_accessors(custom1));
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeRotateRotation"_ustr, FN_NODE_ROTATE_ROTATION);
ntype.ui_name = "Rotate Rotation";
ntype.ui_description = "Apply a secondary rotation to a given rotation value";
ntype.enum_name_legacy = "ROTATE_ROTATION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_rotate_rotation_cc

View File

@@ -0,0 +1,54 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_quaternion.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_rotate_vector_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.is_function_node();
b.add_input<decl::Vector>("Vector"_ustr).is_default_link_socket();
b.add_output<decl::Vector>("Vector"_ustr).align_with_previous();
b.add_input<decl::Rotation>("Rotation"_ustr);
};
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "rotate_vector", in, out);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI2_SO<float3, math::Quaternion, float3>(
"Rotate Vector",
[](float3 vector, math::Quaternion quat) { return math::transform_point(quat, vector); });
builder.set_matching_fn(fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeRotateVector"_ustr, FN_NODE_ROTATE_VECTOR);
ntype.ui_name = "Rotate Vector";
ntype.ui_description = "Apply a rotation to a given vector";
ntype.enum_name_legacy = "ROTATE_VECTOR";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_rotate_vector_cc

View File

@@ -0,0 +1,118 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_axis_angle.hh"
#include "BLI_math_quaternion.hh"
#include "NOD_inverse_eval_params.hh"
#include "NOD_value_elem_eval.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_rotation_to_axis_angle_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Rotation>("Rotation"_ustr);
b.add_output<decl::Vector>("Axis"_ustr);
b.add_output<decl::Float>("Angle"_ustr).subtype(PROP_ANGLE);
};
class QuaterniontoAxisAngleFunction : public mf::MultiFunction {
public:
QuaterniontoAxisAngleFunction()
{
static mf::Signature signature_;
mf::SignatureBuilder builder{"Quaternion to Axis Angle", signature_};
builder.single_input<math::Quaternion>("Quaternion");
builder.single_output<float3>("Axis");
builder.single_output<float>("Angle");
this->set_signature(&signature_);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArraySpan<math::Quaternion> quaternions =
params.readonly_single_input<math::Quaternion>(0, "Quaternion");
MutableSpan<float3> axes = params.uninitialized_single_output<float3>(1, "Axis");
MutableSpan<float> angles = params.uninitialized_single_output<float>(2, "Angle");
mask.foreach_index([&](const int64_t i) {
const math::AxisAngle axis_angle = math::to_axis_angle(quaternions[i]);
axes[i] = axis_angle.axis();
angles[i] = axis_angle.angle().radian();
});
}
};
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "rotation_to_axis_angle", in, out);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static QuaterniontoAxisAngleFunction fn;
builder.set_matching_fn(fn);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
const RotationElem rotation_elem = params.get_input_elem<RotationElem>("Rotation"_ustr);
params.set_output_elem("Axis"_ustr, rotation_elem.axis);
params.set_output_elem("Angle"_ustr, rotation_elem.angle);
}
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams &params)
{
using namespace value_elem;
RotationElem rotation_elem;
rotation_elem.axis = params.get_output_elem<VectorElem>("Axis"_ustr);
rotation_elem.angle = params.get_output_elem<FloatElem>("Angle"_ustr);
if (rotation_elem) {
rotation_elem.euler = VectorElem::all();
}
params.set_input_elem("Rotation"_ustr, rotation_elem);
}
static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
{
const float3 axis = params.get_output<float3>("Axis"_ustr);
const float angle = params.get_output<float>("Angle"_ustr);
math::Quaternion rotation;
if (math::is_zero(axis)) {
rotation = math::Quaternion::identity();
}
else {
rotation = math::to_quaternion(math::AxisAngle(math::normalize(axis), angle));
}
params.set_input("Rotation"_ustr, rotation);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(
&ntype, "FunctionNodeRotationToAxisAngle"_ustr, FN_NODE_ROTATION_TO_AXIS_ANGLE);
ntype.ui_name = "Rotation to Axis Angle";
ntype.ui_description = "Convert a rotation to axis angle components";
ntype.enum_name_legacy = "ROTATION_TO_AXIS_ANGLE";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;
ntype.eval_inverse = node_eval_inverse;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_rotation_to_axis_angle_cc

View File

@@ -0,0 +1,82 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_euler.hh"
#include "NOD_inverse_eval_params.hh"
#include "NOD_value_elem_eval.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_rotation_to_euler_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Rotation>("Rotation"_ustr);
b.add_output<decl::Vector>("Euler"_ustr).subtype(PROP_EULER);
};
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "rotation_to_euler", in, out);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI1_SO<math::Quaternion, float3>(
"Quaternion to Euler XYZ", [](math::Quaternion quat) { return math::to_euler(quat); });
builder.set_matching_fn(fn);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
const RotationElem rotation_elem = params.get_input_elem<RotationElem>("Rotation"_ustr);
params.set_output_elem("Euler"_ustr, rotation_elem.euler);
}
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams &params)
{
using namespace value_elem;
RotationElem rotation_elem;
rotation_elem.euler = params.get_output_elem<VectorElem>("Euler"_ustr);
if (rotation_elem) {
rotation_elem.axis = VectorElem::all();
rotation_elem.angle = FloatElem::all();
}
params.set_input_elem("Rotation"_ustr, rotation_elem);
}
static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
{
const float3 euler = params.get_output<float3>("Euler"_ustr);
const math::Quaternion rotation = math::to_quaternion(math::EulerXYZ(euler));
params.set_input("Rotation"_ustr, rotation);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeRotationToEuler"_ustr, FN_NODE_ROTATION_TO_EULER);
ntype.ui_name = "Rotation to Euler";
ntype.ui_description = "Convert a standard rotation value to an Euler rotation";
ntype.enum_name_legacy = "ROTATION_TO_EULER";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;
ntype.eval_inverse = node_eval_inverse;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_rotation_to_euler_cc

View File

@@ -0,0 +1,85 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_quaternion_types.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_rotation_to_quaternion_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Rotation>("Rotation"_ustr);
b.add_output<decl::Float>("W"_ustr);
b.add_output<decl::Float>("X"_ustr);
b.add_output<decl::Float>("Y"_ustr);
b.add_output<decl::Float>("Z"_ustr);
};
class SeparateQuaternionFunction : public mf::MultiFunction {
public:
SeparateQuaternionFunction()
{
static mf::Signature signature_;
mf::SignatureBuilder builder{"Rotation to Quaternion", signature_};
builder.single_input<math::Quaternion>("Quaternion");
builder.single_output<float>("W");
builder.single_output<float>("X");
builder.single_output<float>("Y");
builder.single_output<float>("Z");
this->set_signature(&signature_);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArraySpan<math::Quaternion> quats = params.readonly_single_input<math::Quaternion>(
0, "Quaternion");
MutableSpan<float> w = params.uninitialized_single_output<float>(1, "W");
MutableSpan<float> x = params.uninitialized_single_output<float>(2, "X");
MutableSpan<float> y = params.uninitialized_single_output<float>(3, "Y");
MutableSpan<float> z = params.uninitialized_single_output<float>(4, "Z");
mask.foreach_index([&](const int64_t i) {
const math::Quaternion quat = quats[i];
w[i] = quat.w;
x[i] = quat.x;
y[i] = quat.y;
z[i] = quat.z;
});
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static SeparateQuaternionFunction fn;
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "rotation_to_quaternion", in, out);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(
&ntype, "FunctionNodeRotationToQuaternion"_ustr, FN_NODE_ROTATION_TO_QUATERNION);
ntype.ui_name = "Rotation to Quaternion";
ntype.ui_description = "Retrieve the quaternion components representing a rotation";
ntype.enum_name_legacy = "ROTATION_TO_QUATERNION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_rotation_to_quaternion_cc

View File

@@ -0,0 +1,260 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
#include "BLI_math_color.h"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_rna_define.hh"
#include "RNA_enum_types.hh"
namespace blender::nodes::node_fn_separate_color_cc {
NODE_STORAGE_FUNCS(NodeCombSepColor)
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Color>("Color"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f});
b.add_output<decl::Float>("Red"_ustr).label_fn([](const bNode &node) {
switch (node_storage(node).mode) {
case NODE_COMBSEP_COLOR_RGB:
default:
return IFACE_("Red");
case NODE_COMBSEP_COLOR_HSV:
case NODE_COMBSEP_COLOR_HSL:
return IFACE_("Hue");
}
});
b.add_output<decl::Float>("Green"_ustr).label_fn([](const bNode &node) {
switch (node_storage(node).mode) {
case NODE_COMBSEP_COLOR_RGB:
default:
return IFACE_("Green");
case NODE_COMBSEP_COLOR_HSV:
case NODE_COMBSEP_COLOR_HSL:
return IFACE_("Saturation");
}
});
b.add_output<decl::Float>("Blue"_ustr).label_fn([](const bNode &node) {
switch (node_storage(node).mode) {
case NODE_COMBSEP_COLOR_RGB:
default:
return IFACE_("Blue");
case NODE_COMBSEP_COLOR_HSV:
return CTX_IFACE_(BLT_I18NCONTEXT_COLOR, "Value");
case NODE_COMBSEP_COLOR_HSL:
return IFACE_("Lightness");
}
});
b.add_output<decl::Float>("Alpha"_ustr);
};
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeCombSepColor *data = MEM_new<NodeCombSepColor>(__func__);
data->mode = NODE_COMBSEP_COLOR_RGB;
node->storage = data;
}
class SeparateRGBAFunction : public mf::MultiFunction {
public:
SeparateRGBAFunction()
{
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Separate Color", signature};
builder.single_input<ColorGeometry4f>("Color");
builder.single_output<float>("Red", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Green", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Blue", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Alpha", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArray<ColorGeometry4f> &colors = params.readonly_single_input<ColorGeometry4f>(0,
"Color");
MutableSpan<float> red = params.uninitialized_single_output_if_required<float>(1, "Red");
MutableSpan<float> green = params.uninitialized_single_output_if_required<float>(2, "Green");
MutableSpan<float> blue = params.uninitialized_single_output_if_required<float>(3, "Blue");
MutableSpan<float> alpha = params.uninitialized_single_output_if_required<float>(4, "Alpha");
std::array<MutableSpan<float>, 4> outputs = {red, green, blue, alpha};
Vector<int> used_outputs;
if (!red.is_empty()) {
used_outputs.append(0);
}
if (!green.is_empty()) {
used_outputs.append(1);
}
if (!blue.is_empty()) {
used_outputs.append(2);
}
if (!alpha.is_empty()) {
used_outputs.append(3);
}
devirtualize_varray(colors, [&](auto colors) {
mask.foreach_segment_optimized([&](const auto segment) {
const int used_outputs_num = used_outputs.size();
const int *used_outputs_data = used_outputs.data();
for (const int64_t i : segment) {
const ColorGeometry4f &color = colors[i];
for (const int out_i : IndexRange(used_outputs_num)) {
const int channel = used_outputs_data[out_i];
outputs[channel][i] = color[channel];
}
}
});
});
}
};
class SeparateHSVAFunction : public mf::MultiFunction {
public:
SeparateHSVAFunction()
{
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Separate Color", signature};
builder.single_input<ColorGeometry4f>("Color");
builder.single_output<float>("Hue");
builder.single_output<float>("Saturation");
builder.single_output<float>("Value");
builder.single_output<float>("Alpha", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArray<ColorGeometry4f> &colors = params.readonly_single_input<ColorGeometry4f>(0,
"Color");
MutableSpan<float> hue = params.uninitialized_single_output<float>(1, "Hue");
MutableSpan<float> saturation = params.uninitialized_single_output<float>(2, "Saturation");
MutableSpan<float> value = params.uninitialized_single_output<float>(3, "Value");
MutableSpan<float> alpha = params.uninitialized_single_output_if_required<float>(4, "Alpha");
mask.foreach_index_optimized<int64_t>([&](const int64_t i) {
rgb_to_hsv(colors[i].r, colors[i].g, colors[i].b, &hue[i], &saturation[i], &value[i]);
});
if (!alpha.is_empty()) {
mask.foreach_index_optimized<int64_t>([&](const int64_t i) { alpha[i] = colors[i].a; });
}
}
};
class SeparateHSLAFunction : public mf::MultiFunction {
public:
SeparateHSLAFunction()
{
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Separate Color", signature};
builder.single_input<ColorGeometry4f>("Color");
builder.single_output<float>("Hue");
builder.single_output<float>("Saturation");
builder.single_output<float>("Lightness");
builder.single_output<float>("Alpha", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArray<ColorGeometry4f> &colors = params.readonly_single_input<ColorGeometry4f>(0,
"Color");
MutableSpan<float> hue = params.uninitialized_single_output<float>(1, "Hue");
MutableSpan<float> saturation = params.uninitialized_single_output<float>(2, "Saturation");
MutableSpan<float> lightness = params.uninitialized_single_output<float>(3, "Lightness");
MutableSpan<float> alpha = params.uninitialized_single_output_if_required<float>(4, "Alpha");
mask.foreach_index_optimized<int64_t>([&](const int64_t i) {
rgb_to_hsl(colors[i].r, colors[i].g, colors[i].b, &hue[i], &saturation[i], &lightness[i]);
});
if (!alpha.is_empty()) {
mask.foreach_index_optimized<int64_t>([&](const int64_t i) { alpha[i] = colors[i].a; });
}
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const NodeCombSepColor &storage = node_storage(builder.node());
switch (storage.mode) {
case NODE_COMBSEP_COLOR_RGB: {
static SeparateRGBAFunction fn;
builder.set_matching_fn(fn);
break;
}
case NODE_COMBSEP_COLOR_HSV: {
static SeparateHSVAFunction fn;
builder.set_matching_fn(fn);
break;
}
case NODE_COMBSEP_COLOR_HSL: {
static SeparateHSLAFunction fn;
builder.set_matching_fn(fn);
break;
}
default: {
BLI_assert_unreachable();
break;
}
}
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"mode",
"Mode",
"Mode of color processing",
rna_enum_node_combsep_color_items,
NOD_storage_enum_accessors(mode));
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeSeparateColor"_ustr, FN_NODE_SEPARATE_COLOR);
ntype.ui_name = "Separate Color";
ntype.ui_description = "Split a color into separate channels, based on a particular color model";
ntype.enum_name_legacy = "SEPARATE_COLOR";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
bke::node_type_storage(
ntype, "NodeCombSepColor", node_free_standard_storage, node_copy_standard_storage);
ntype.build_multi_function = node_build_multi_function;
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_separate_color_cc

View File

@@ -0,0 +1,302 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_inverse_eval_params.hh"
#include "NOD_value_elem_eval.hh"
#include "GPU_material.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_separate_matrix_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.use_custom_socket_order();
b.allow_any_socket_order();
PanelDeclarationBuilder &column_a = b.add_panel("Column 1"_ustr).default_closed(true);
column_a.add_output<decl::Float>("Column 1 Row 1"_ustr);
column_a.add_output<decl::Float>("Column 1 Row 2"_ustr);
column_a.add_output<decl::Float>("Column 1 Row 3"_ustr);
column_a.add_output<decl::Float>("Column 1 Row 4"_ustr);
PanelDeclarationBuilder &column_b = b.add_panel("Column 2"_ustr).default_closed(true);
column_b.add_output<decl::Float>("Column 2 Row 1"_ustr);
column_b.add_output<decl::Float>("Column 2 Row 2"_ustr);
column_b.add_output<decl::Float>("Column 2 Row 3"_ustr);
column_b.add_output<decl::Float>("Column 2 Row 4"_ustr);
PanelDeclarationBuilder &column_c = b.add_panel("Column 3"_ustr).default_closed(true);
column_c.add_output<decl::Float>("Column 3 Row 1"_ustr);
column_c.add_output<decl::Float>("Column 3 Row 2"_ustr);
column_c.add_output<decl::Float>("Column 3 Row 3"_ustr);
column_c.add_output<decl::Float>("Column 3 Row 4"_ustr);
PanelDeclarationBuilder &column_d = b.add_panel("Column 4"_ustr).default_closed(true);
column_d.add_output<decl::Float>("Column 4 Row 1"_ustr);
column_d.add_output<decl::Float>("Column 4 Row 2"_ustr);
column_d.add_output<decl::Float>("Column 4 Row 3"_ustr);
column_d.add_output<decl::Float>("Column 4 Row 4"_ustr);
b.add_input<decl::Matrix>("Matrix"_ustr);
}
static void copy_with_stride(const IndexMask &mask,
const Span<float> src,
const int64_t src_step,
const int64_t src_begin,
const int64_t dst_step,
const int64_t dst_begin,
MutableSpan<float> dst)
{
if (dst.is_empty()) {
return;
}
BLI_assert(src_begin < src_step);
BLI_assert(dst_begin < dst_step);
mask.foreach_index_optimized<int>([&](const int64_t index) {
dst[dst_begin + dst_step * index] = src[src_begin + src_step * index];
});
}
class SeparateMatrixFunction : public mf::MultiFunction {
public:
SeparateMatrixFunction()
{
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Separate Matrix", signature};
builder.single_input<float4x4>("Matrix");
builder.single_output<float>("Column 1 Row 1", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 1 Row 2", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 1 Row 3", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 1 Row 4", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 2 Row 1", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 2 Row 2", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 2 Row 3", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 2 Row 4", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 3 Row 1", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 3 Row 2", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 3 Row 3", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 3 Row 4", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 4 Row 1", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 4 Row 2", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 4 Row 3", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Column 4 Row 4", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArray<float4x4> matrices = params.readonly_single_input<float4x4>(0, "Matrix");
MutableSpan<float> column_1_row_1 = params.uninitialized_single_output_if_required<float>(
1, "Column 1 Row 1");
MutableSpan<float> column_1_row_2 = params.uninitialized_single_output_if_required<float>(
2, "Column 1 Row 2");
MutableSpan<float> column_1_row_3 = params.uninitialized_single_output_if_required<float>(
3, "Column 1 Row 3");
MutableSpan<float> column_1_row_4 = params.uninitialized_single_output_if_required<float>(
4, "Column 1 Row 4");
MutableSpan<float> column_2_row_1 = params.uninitialized_single_output_if_required<float>(
5, "Column 2 Row 1");
MutableSpan<float> column_2_row_2 = params.uninitialized_single_output_if_required<float>(
6, "Column 2 Row 2");
MutableSpan<float> column_2_row_3 = params.uninitialized_single_output_if_required<float>(
7, "Column 2 Row 3");
MutableSpan<float> column_2_row_4 = params.uninitialized_single_output_if_required<float>(
8, "Column 2 Row 4");
MutableSpan<float> column_3_row_1 = params.uninitialized_single_output_if_required<float>(
9, "Column 3 Row 1");
MutableSpan<float> column_3_row_2 = params.uninitialized_single_output_if_required<float>(
10, "Column 3 Row 2");
MutableSpan<float> column_3_row_3 = params.uninitialized_single_output_if_required<float>(
11, "Column 3 Row 3");
MutableSpan<float> column_3_row_4 = params.uninitialized_single_output_if_required<float>(
12, "Column 3 Row 4");
MutableSpan<float> column_4_row_1 = params.uninitialized_single_output_if_required<float>(
13, "Column 4 Row 1");
MutableSpan<float> column_4_row_2 = params.uninitialized_single_output_if_required<float>(
14, "Column 4 Row 2");
MutableSpan<float> column_4_row_3 = params.uninitialized_single_output_if_required<float>(
15, "Column 4 Row 3");
MutableSpan<float> column_4_row_4 = params.uninitialized_single_output_if_required<float>(
16, "Column 4 Row 4");
if (const std::optional<float4x4> single = matrices.get_if_single()) {
const float4x4 matrix = *single;
column_1_row_1.fill(matrix[0][0]);
column_1_row_2.fill(matrix[0][1]);
column_1_row_3.fill(matrix[0][2]);
column_1_row_4.fill(matrix[0][3]);
column_2_row_1.fill(matrix[1][0]);
column_2_row_2.fill(matrix[1][1]);
column_2_row_3.fill(matrix[1][2]);
column_2_row_4.fill(matrix[1][3]);
column_3_row_1.fill(matrix[2][0]);
column_3_row_2.fill(matrix[2][1]);
column_3_row_3.fill(matrix[2][2]);
column_3_row_4.fill(matrix[2][3]);
column_4_row_1.fill(matrix[3][0]);
column_4_row_2.fill(matrix[3][1]);
column_4_row_3.fill(matrix[3][2]);
column_4_row_4.fill(matrix[3][3]);
return;
}
const VArraySpan<float4x4> span_matrices(matrices);
const Span<float> components = span_matrices.cast<float>();
copy_with_stride(mask, components, 16, 0, 1, 0, column_1_row_1);
copy_with_stride(mask, components, 16, 1, 1, 0, column_1_row_2);
copy_with_stride(mask, components, 16, 2, 1, 0, column_1_row_3);
copy_with_stride(mask, components, 16, 3, 1, 0, column_1_row_4);
copy_with_stride(mask, components, 16, 4, 1, 0, column_2_row_1);
copy_with_stride(mask, components, 16, 5, 1, 0, column_2_row_2);
copy_with_stride(mask, components, 16, 6, 1, 0, column_2_row_3);
copy_with_stride(mask, components, 16, 7, 1, 0, column_2_row_4);
copy_with_stride(mask, components, 16, 8, 1, 0, column_3_row_1);
copy_with_stride(mask, components, 16, 9, 1, 0, column_3_row_2);
copy_with_stride(mask, components, 16, 10, 1, 0, column_3_row_3);
copy_with_stride(mask, components, 16, 11, 1, 0, column_3_row_4);
copy_with_stride(mask, components, 16, 12, 1, 0, column_4_row_1);
copy_with_stride(mask, components, 16, 13, 1, 0, column_4_row_2);
copy_with_stride(mask, components, 16, 14, 1, 0, column_4_row_3);
copy_with_stride(mask, components, 16, 15, 1, 0, column_4_row_4);
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const static SeparateMatrixFunction fn;
builder.set_matching_fn(fn);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
const MatrixElem matrix_elem = params.get_input_elem<MatrixElem>("Matrix"_ustr);
std::array<std::array<FloatElem, 4>, 4> output_elems;
output_elems[3][0] = matrix_elem.translation.x;
output_elems[3][1] = matrix_elem.translation.y;
output_elems[3][2] = matrix_elem.translation.z;
if (matrix_elem.rotation || matrix_elem.scale) {
for (const int col : IndexRange(3)) {
for (const int row : IndexRange(3)) {
output_elems[col][row] = FloatElem::all();
}
}
}
if (matrix_elem.any_non_transform) {
for (const int col : IndexRange(4)) {
output_elems[col][3] = FloatElem::all();
}
}
for (const int col : IndexRange(4)) {
for (const int row : IndexRange(4)) {
const bNodeSocket &socket = params.node.output_socket(col * 4 + row);
params.set_output_elem(socket.identifier_ustr(), output_elems[col][row]);
}
}
}
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams &params)
{
using namespace value_elem;
std::array<std::array<FloatElem, 4>, 4> output_elems;
for (const int col : IndexRange(4)) {
for (const int row : IndexRange(4)) {
const bNodeSocket &socket = params.node.output_socket(col * 4 + row);
output_elems[col][row] = params.get_output_elem<FloatElem>(socket.identifier_ustr());
}
}
MatrixElem matrix_elem;
matrix_elem.translation.x = output_elems[3][0];
matrix_elem.translation.y = output_elems[3][1];
matrix_elem.translation.z = output_elems[3][2];
bool any_inner_3x3 = false;
for (const int col : IndexRange(3)) {
for (const int row : IndexRange(3)) {
any_inner_3x3 |= output_elems[col][row];
}
}
if (any_inner_3x3) {
matrix_elem.rotation = RotationElem::all();
matrix_elem.scale = VectorElem::all();
}
const bool any_non_transform = output_elems[0][3] || output_elems[1][3] || output_elems[2][3] ||
output_elems[3][3];
if (any_non_transform) {
matrix_elem.any_non_transform = FloatElem::all();
}
params.set_input_elem("Matrix"_ustr, matrix_elem);
}
static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
{
float4x4 matrix;
for (const int col : IndexRange(4)) {
for (const int row : IndexRange(4)) {
const bNodeSocket &socket = params.node.output_socket(col * 4 + row);
matrix[col][row] = params.get_output<float>(socket.identifier_ustr());
}
}
params.set_input("Matrix"_ustr, matrix);
}
static int node_gpu_material(GPUMaterial *material,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *inputs,
GPUNodeStack *outputs)
{
return GPU_stack_link(material, node, "node_function_separate_matrix", inputs, outputs);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeSeparateMatrix"_ustr, FN_NODE_SEPARATE_MATRIX);
ntype.ui_name = "Separate Matrix";
ntype.ui_description = "Split a 4x4 matrix into its individual values";
ntype.enum_name_legacy = "SEPARATE_MATRIX";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;
ntype.eval_inverse = node_eval_inverse;
ntype.gpu_fn = node_gpu_material;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_separate_matrix_cc

View File

@@ -0,0 +1,125 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "BLI_math_rotation.hh"
#include "NOD_inverse_eval_params.hh"
#include "NOD_value_elem_eval.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_separate_transform_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Matrix>("Transform"_ustr);
b.add_output<decl::Vector>("Translation"_ustr).subtype(PROP_TRANSLATION);
b.add_output<decl::Rotation>("Rotation"_ustr);
b.add_output<decl::Vector>("Scale"_ustr).subtype(PROP_XYZ);
};
class SeparateTransformFunction : public mf::MultiFunction {
public:
SeparateTransformFunction()
{
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Separate Transform", signature};
builder.single_input<float4x4>("Transform");
builder.single_output<float3>("Translation", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<math::Quaternion>("Rotation", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float3>("Scale", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArraySpan transforms = params.readonly_single_input<float4x4>(0, "Transform");
MutableSpan translation = params.uninitialized_single_output_if_required<float3>(
1, "Translation");
MutableSpan rotation = params.uninitialized_single_output_if_required<math::Quaternion>(
2, "Rotation");
MutableSpan scale = params.uninitialized_single_output_if_required<float3>(3, "Scale");
if (!translation.is_empty()) {
mask.foreach_index_optimized<int64_t>(
[&](const int64_t i) { translation[i] = transforms[i].location(); });
}
if (rotation.is_empty() && !scale.is_empty()) {
mask.foreach_index([&](const int64_t i) { scale[i] = math::to_scale(transforms[i]); });
}
else if (!rotation.is_empty() && scale.is_empty()) {
mask.foreach_index([&](const int64_t i) {
rotation[i] = math::normalized_to_quaternion_safe(
math::normalize(float3x3(transforms[i])));
});
}
else if (!rotation.is_empty() && !scale.is_empty()) {
mask.foreach_index([&](const int64_t i) {
const float3x3 normalized_mat = math::normalize_and_get_size(float3x3(transforms[i]),
scale[i]);
rotation[i] = math::normalized_to_quaternion_safe(normalized_mat);
});
}
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static SeparateTransformFunction fn;
builder.set_matching_fn(fn);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
const MatrixElem matrix_elem = params.get_input_elem<MatrixElem>("Transform"_ustr);
params.set_output_elem("Translation"_ustr, matrix_elem.translation);
params.set_output_elem("Rotation"_ustr, matrix_elem.rotation);
params.set_output_elem("Scale"_ustr, matrix_elem.scale);
}
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams &params)
{
using namespace value_elem;
MatrixElem transform_elem;
transform_elem.translation = params.get_output_elem<VectorElem>("Translation"_ustr);
transform_elem.rotation = params.get_output_elem<RotationElem>("Rotation"_ustr);
transform_elem.scale = params.get_output_elem<VectorElem>("Scale"_ustr);
params.set_input_elem("Transform"_ustr, transform_elem);
}
static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
{
const float3 translation = params.get_output<float3>("Translation"_ustr);
const math::Quaternion rotation = params.get_output<math::Quaternion>("Rotation"_ustr);
const float3 scale = params.get_output<float3>("Scale"_ustr);
params.set_input("Transform"_ustr,
math::from_loc_rot_scale<float4x4>(translation, rotation, scale));
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeSeparateTransform"_ustr, FN_NODE_SEPARATE_TRANSFORM);
ntype.ui_name = "Separate Transform";
ntype.ui_description =
"Split a transformation matrix into a translation vector, a rotation, and a scale vector";
ntype.enum_name_legacy = "SEPARATE_TRANSFORM";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;
ntype.eval_inverse = node_eval_inverse;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_separate_transform_cc

View File

@@ -0,0 +1,127 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_string_utf8.h"
#include "BLI_vector.hh"
#include "BKE_node_runtime.hh"
#include "NOD_socket_search_link.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_set_string_case_cc {
enum class Case {
Uppercase = 0,
Lowercase = 1,
};
static const EnumPropertyItem case_items[] = {
{int(Case::Uppercase),
"UPPERCASE",
0,
N_("Uppercase"),
N_("Convert all characters to uppercase")},
{int(Case::Lowercase),
"LOWERCASE",
0,
N_("Lowercase"),
N_("Convert all characters to lowercase")},
{},
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::String>("String"_ustr).optional_label();
b.add_output<decl::String>("String"_ustr).align_with_previous();
b.add_input<decl::Menu>("Case"_ustr).static_items(case_items).optional_label();
}
static std::string apply_string_case(const std::string &s, const Case mode)
{
if (s.empty()) {
return s;
}
size_t len_bytes;
const size_t len_chars = BLI_strlen_utf8_ex(s.c_str(), &len_bytes);
Array<char32_t, 64> utf32(len_chars + 1);
BLI_str_utf8_as_utf32(utf32.data(), s.c_str(), utf32.size());
for (size_t i = 0; i < len_chars; i++) {
const char32_t c = utf32[i];
switch (mode) {
case Case::Uppercase:
utf32[i] = BLI_str_utf32_char_to_upper(c);
break;
case Case::Lowercase:
utf32[i] = BLI_str_utf32_char_to_lower(c);
break;
}
}
Array<char, 64> out(len_chars * 4 + 1);
BLI_str_utf32_as_utf8(out.data(), utf32.data(), out.size());
return std::string(out.data());
}
static void node_gather_link_search_ops(GatherLinkSearchOpParams &params)
{
if (!params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_STRING)) {
return;
}
if (params.in_out() == SOCK_IN) {
for (const EnumPropertyItem *item = case_items; item->identifier != nullptr; item++) {
if (item->name != nullptr && item->identifier[0] != '\0') {
const int value = item->value;
params.add_item(IFACE_(item->name), [value](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeSetStringCase"_ustr);
bke::node_find_socket(node, SOCK_IN, "Case"_ustr)
->default_value_typed<bNodeSocketValueMenu>()
->value = value;
params.update_and_connect_available_socket(node, "String"_ustr);
});
}
}
}
else {
params.add_item(IFACE_("String"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeSetStringCase"_ustr);
params.update_and_connect_available_socket(node, "String"_ustr);
});
}
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI2_SO<std::string, MenuValue, std::string>(
"String Case", [](const std::string &s, MenuValue mode) -> std::string {
return apply_string_case(s, Case(mode.value));
});
builder.set_matching_fn(&fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeSetStringCase"_ustr);
ntype.ui_name = "Set String Case";
ntype.ui_description = "Convert the case of a string";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gather_link_search_ops = node_gather_link_search_ops;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_set_string_case_cc

View File

@@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string_utf8.h"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_slice_string_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::String>("String"_ustr).optional_label();
b.add_output<decl::String>("String"_ustr).align_with_previous();
b.add_input<decl::Int>("Position"_ustr);
b.add_input<decl::Int>("Length"_ustr).min(0).default_value(10);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto slice_fn = mf::build::SI3_SO<std::string, int, int, std::string>(
"Slice", [](const std::string &str, int a, int b) {
const int start = BLI_str_utf8_offset_from_index(str.c_str(), str.size(), std::max(0, a));
const int end = BLI_str_utf8_offset_from_index(
str.c_str(), str.size(), std::max(0, a + b));
return str.substr(start, std::max<int>(end - start, 0));
});
builder.set_matching_fn(&slice_fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeSliceString"_ustr, FN_NODE_SLICE_STRING);
ntype.ui_name = "Slice String";
ntype.ui_description = "Extract a string segment from a larger string";
ntype.enum_name_legacy = "SLICE_STRING";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_slice_string_cc

View File

@@ -0,0 +1,66 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string_utf8.h"
#include "../geometry/node_geometry_util.hh"
#include "NOD_geometry_nodes_list.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_split_string_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::String>("String"_ustr).optional_label();
b.add_input<decl::String>("Separator"_ustr).optional_label();
b.add_output<decl::String>("List"_ustr)
.structure_type(StructureType::List)
.description(
"The parts of the input string. This contains at least one element which may be empty");
}
static Vector<std::string> split_string(const StringRef original_str, const StringRef separator)
{
if (separator.is_empty()) {
return {original_str};
}
StringRef remaining = original_str;
Vector<std::string> result;
while (true) {
const int separator_pos = remaining.find(separator);
if (separator_pos == StringRef::not_found) {
result.append(remaining);
return result;
}
result.append(remaining.substr(0, separator_pos));
remaining = remaining.substr(separator_pos + separator.size());
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
const std::string str = params.extract_input<std::string>("String"_ustr);
const std::string separator = params.extract_input<std::string>("Separator"_ustr);
Vector<std::string> list = split_string(str, separator);
params.set_output("List"_ustr, List<std::string>::from_container(std::move(list)));
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeSplitString"_ustr);
ntype.ui_name = "Split String";
ntype.ui_description = "Split a string into a list using a separator";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_split_string_cc

View File

@@ -0,0 +1,40 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string_utf8.h"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_string_length_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::String>("String"_ustr).optional_label();
b.add_output<decl::Int>("Length"_ustr);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto str_len_fn = mf::build::SI1_SO<std::string, int>(
"String Length", [](const std::string &a) { return BLI_strlen_utf8(a.c_str()); });
builder.set_matching_fn(&str_len_fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeStringLength"_ustr, FN_NODE_STRING_LENGTH);
ntype.ui_name = "String Length";
ntype.ui_description = "Output the number of characters in the given string";
ntype.enum_name_legacy = "STRING_LENGTH";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_string_length_cc

View File

@@ -0,0 +1,168 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string_utf8.h"
#include "fast_float.h"
#include "node_function_util.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include <charconv>
namespace blender::nodes::node_fn_string_to_value_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::String>("String"_ustr).optional_label();
const bNode *node = b.node_or_null();
if (node != nullptr) {
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
b.add_input<decl::Int>("Base"_ustr)
.min(2)
.max(36)
.default_value(10)
.description("Numeric base for the input string (e.g. 2 for binary, 16 for hexadecimal)")
.available(data_type == SOCK_INT);
b.add_output(data_type, "Value"_ustr);
}
b.add_output<decl::Int>("Length"_ustr);
}
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
{
static auto str_to_float_fn = mf::build::SI1_SO2<std::string, float, int>(
"String to Value", [](const std::string &s, float &value, int &length) -> void {
const auto result = fast_float::from_chars(s.data(), s.data() + s.size(), value);
if (result.ec != std::errc()) {
value = 0.0f;
length = 0;
return;
}
length = BLI_strnlen_utf8(s.data(), result.ptr - s.data());
});
static auto str_to_int_fn = mf::build::SI2_SO2<std::string, int, int, int>(
"String to Value", [](const std::string &s, int base, int &value, int &length) -> void {
if (base < 2 || base > 36) {
value = 0;
length = 0;
return;
}
const auto result = std::from_chars(s.data(), s.data() + s.size(), value, base);
if (result.ec != std::errc()) {
value = 0;
length = 0;
return;
}
length = BLI_strnlen_utf8(s.data(), result.ptr - s.data());
});
switch (eNodeSocketDatatype(bnode.custom1)) {
case SOCK_FLOAT:
return &str_to_float_fn;
case SOCK_INT:
return &str_to_int_fn;
default:
BLI_assert_unreachable();
return nullptr;
}
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const mf::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
static void node_init(bNodeTree *, bNode *node)
{
node->custom1 = SOCK_FLOAT;
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const eNodeSocketDatatype socket_type = params.other_socket().type;
if (params.in_out() == SOCK_IN) {
if (socket_type == SOCK_STRING) {
params.add_item(IFACE_("String"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeStringToValue"_ustr);
params.update_and_connect_available_socket(node, "String"_ustr);
});
}
}
else if (params.in_out() == SOCK_OUT) {
if (ELEM(socket_type, SOCK_INT, SOCK_BOOLEAN)) {
params.add_item(IFACE_("Value"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeStringToValue"_ustr);
node.custom1 = SOCK_INT;
params.update_and_connect_available_socket(node, "Value"_ustr);
});
}
else if (params.node_tree().typeinfo->validate_link(SOCK_FLOAT, socket_type)) {
params.add_item(IFACE_("Value"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeStringToValue"_ustr);
node.custom1 = SOCK_FLOAT;
params.update_and_connect_available_socket(node, "Value"_ustr);
});
}
if (socket_type == SOCK_INT) {
params.add_item(IFACE_("Length"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeStringToValue"_ustr);
params.update_and_connect_available_socket(node, "Length"_ustr);
});
}
}
}
static void node_layout(ui::Layout &layout, bContext *, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem data_types[] = {
{SOCK_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, N_("Float"), N_("Floating-point value")},
{SOCK_INT, "INT", ICON_NODE_SOCKET_INT, N_("Integer"), N_("32-bit integer")},
{0, nullptr, 0, nullptr, nullptr}};
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"",
data_types,
NOD_inline_enum_accessors(custom1),
SOCK_FLOAT);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeStringToValue"_ustr);
ntype.ui_name = "String to Value";
ntype.ui_description = "Derive a numeric value from a given string representation";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.draw_buttons = node_layout;
ntype.build_multi_function = node_build_multi_function;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_string_to_value_cc

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "GPU_material.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_transform_direction_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.is_function_node();
b.add_input<decl::Vector>("Direction"_ustr).subtype(PROP_XYZ);
b.add_output<decl::Vector>("Direction"_ustr).subtype(PROP_XYZ).align_with_previous();
b.add_input<decl::Matrix>("Transform"_ustr);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI2_SO<float3, float4x4, float3>(
"Transform Direction", [](float3 direction, float4x4 matrix) {
return math::transform_direction(matrix, direction);
});
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *material,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *inputs,
GPUNodeStack *outputs)
{
return GPU_stack_link(material, node, "node_function_transform_direction", inputs, outputs);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(
&ntype, "FunctionNodeTransformDirection"_ustr, FN_NODE_TRANSFORM_DIRECTION);
ntype.ui_name = "Transform Direction";
ntype.ui_description =
"Apply a transformation matrix (excluding translation) to the given vector";
ntype.enum_name_legacy = "TRANSFORM_DIRECTION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.gpu_fn = node_gpu_material;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_transform_direction_cc

View File

@@ -0,0 +1,55 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "GPU_material.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_transform_point_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.is_function_node();
b.add_input<decl::Vector>("Vector"_ustr).subtype(PROP_XYZ).is_default_link_socket();
b.add_output<decl::Vector>("Vector"_ustr).subtype(PROP_XYZ).align_with_previous();
b.add_input<decl::Matrix>("Transform"_ustr);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI2_SO<float3, float4x4, float3>(
"Transform Point",
[](float3 point, float4x4 matrix) { return math::transform_point(matrix, point); });
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *material,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *inputs,
GPUNodeStack *outputs)
{
return GPU_stack_link(material, node, "node_function_transform_point", inputs, outputs);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeTransformPoint"_ustr, FN_NODE_TRANSFORM_POINT);
ntype.ui_name = "Transform Point";
ntype.ui_description = "Apply a transformation matrix to the given vector";
ntype.enum_name_legacy = "TRANSFORM_POINT";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.gpu_fn = node_gpu_material;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_transform_point_cc

View File

@@ -0,0 +1,54 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "GPU_material.hh"
#include "node_function_util.hh"
namespace blender::nodes::node_fn_transpose_matrix_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.is_function_node();
b.add_input<decl::Matrix>("Matrix"_ustr);
b.add_output<decl::Matrix>("Matrix"_ustr).align_with_previous();
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI1_SO<float4x4, float4x4>(
"Transpose Matrix", [](float4x4 matrix) { return math::transpose(matrix); });
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *material,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *inputs,
GPUNodeStack *outputs)
{
return GPU_stack_link(material, node, "node_function_transpose_matrix", inputs, outputs);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeTransposeMatrix"_ustr, FN_NODE_TRANSPOSE_MATRIX);
ntype.ui_name = "Transpose Matrix";
ntype.ui_description =
"Flip a matrix over its diagonal, turning columns into rows and vice-versa";
ntype.enum_name_legacy = "TRANSPOSE_MATRIX";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
ntype.gpu_fn = node_gpu_material;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_transpose_matrix_cc

View File

@@ -0,0 +1,81 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
namespace blender::nodes::node_fn_trim_string_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::String>("String"_ustr).optional_label();
b.add_output<decl::String>("String"_ustr).align_with_previous();
b.add_input<decl::String>("Characters"_ustr)
.optional_label()
.description("Individual characters to trim. The order of characters does not matter");
b.add_input<decl::Bool>("Whitespace"_ustr)
.default_value(true)
.description("Trim whitespace characters in addition to the provided characters");
{
auto &p = b.add_panel("Limit"_ustr).default_closed(true);
p.add_input<decl::Bool>("Start"_ustr)
.default_value(true)
.description("Trim the beginning of the string");
p.add_input<decl::Bool>("End"_ustr)
.default_value(true)
.description("Trim at the end of the string");
}
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto trim_fn = mf::build::SI5_SO<std::string, std::string, bool, bool, bool, std::string>(
"Trim",
[](const std::string &input_str,
const std::string &characters,
const bool trim_whitespace,
const bool trim_start,
const bool trim_end) {
std::string characters_to_trim = characters;
if (trim_whitespace) {
characters_to_trim.append(" \t\n\r");
}
StringRef str = input_str;
int64_t start = 0;
int64_t end = str.size();
if (trim_start) {
const int64_t i = str.find_first_not_of(characters_to_trim);
if (i != StringRef::not_found) {
start = i;
}
}
if (trim_end) {
const int64_t i = str.find_last_not_of(characters_to_trim);
if (i != StringRef::not_found) {
end = i + 1;
}
}
std::string result = str.substr(start, end - start);
return result;
});
builder.set_matching_fn(&trim_fn);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeTrimString"_ustr);
ntype.ui_name = "Trim String";
ntype.ui_description = "Remove characters from the beginning and end of a string";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_trim_string_cc

View File

@@ -0,0 +1,176 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_function_util.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include <algorithm>
#include <charconv>
#include <iomanip>
#include <sstream>
#include <string>
namespace blender::nodes::node_fn_value_to_string_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
const bNode *node = b.node_or_null();
if (node != nullptr) {
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
b.add_input(data_type, "Value"_ustr);
auto &decimals = b.add_input<decl::Int>("Decimals"_ustr).min(0);
decimals.available(data_type == SOCK_FLOAT);
b.add_input<decl::Int>("Base"_ustr)
.min(2)
.max(36)
.default_value(10)
.description("Numeric base for the output string (e.g. 2 for binary, 16 for hexadecimal)")
.available(data_type == SOCK_INT);
b.add_input<decl::Int>("Padding"_ustr)
.min(0)
.default_value(0)
.description("Minimum number of characters in the output, zero-padded if shorter")
.available(data_type == SOCK_INT);
}
b.add_output<decl::String>("String"_ustr);
}
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
{
static auto float_to_str_fn = mf::build::SI2_SO<float, int, std::string>(
"Value To String", [](float a, int b) {
std::stringstream stream;
stream << std::fixed << std::setprecision(std::max(0, b)) << a;
return stream.str();
});
static auto int_to_str_fn = mf::build::SI3_SO<int, int, int, std::string>(
"Value To String", [](int value, int base, int padding) -> std::string {
if (base < 2 || base > 36) {
return {};
}
padding = std::max(0, padding);
/* Maximum possible string length is reached with -2^31=-2147483648 and base 2. */
char buf[33];
auto [ptr, ec] = std::to_chars(buf, buf + sizeof(buf), value, base);
std::string result(buf, ptr);
if (padding > int(result.size())) {
const size_t needed = size_t(padding) - result.size();
const size_t insert_pos = (!result.empty() && result[0] == '-') ? 1 : 0;
result.insert(insert_pos, needed, '0');
}
return result;
});
switch (bnode.custom1) {
case SOCK_FLOAT:
return &float_to_str_fn;
case SOCK_INT:
return &int_to_str_fn;
}
BLI_assert_unreachable();
return nullptr;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const mf::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = SOCK_FLOAT;
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const eNodeSocketDatatype socket_type = params.other_socket().type;
if (params.in_out() == SOCK_IN) {
if (socket_type == SOCK_INT) {
params.add_item(IFACE_("Value"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeValueToString"_ustr);
node.custom1 = SOCK_INT;
params.update_and_connect_available_socket(node, "Value"_ustr);
});
params.add_item(IFACE_("Decimals"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeValueToString"_ustr);
params.update_and_connect_available_socket(node, "Decimals"_ustr);
});
}
else {
if (params.node_tree().typeinfo->validate_link(socket_type, SOCK_FLOAT)) {
params.add_item(IFACE_("Value"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeValueToString"_ustr);
node.custom1 = SOCK_FLOAT;
params.update_and_connect_available_socket(node, "Value"_ustr);
});
}
}
}
else {
if (socket_type == SOCK_STRING) {
params.add_item(IFACE_("String"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeValueToString"_ustr);
params.update_and_connect_available_socket(node, "String"_ustr);
});
}
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem data_types[] = {
{SOCK_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, "Float", "Floating-point value"},
{SOCK_INT, "INT", ICON_NODE_SOCKET_INT, "Integer", "32-bit integer"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"",
data_types,
NOD_inline_enum_accessors(custom1),
SOCK_FLOAT);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_cmp_node_type_base(&ntype, "FunctionNodeValueToString"_ustr, FN_NODE_VALUE_TO_STRING);
ntype.ui_name = "Value to String";
ntype.ui_description = "Generate a string representation of the given input value";
ntype.enum_name_legacy = "VALUE_TO_STRING";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.draw_buttons = node_layout;
ntype.build_multi_function = node_build_multi_function;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_fn_value_to_string_cc