This moves the bundles and closures features out of experimental, making them an official part of Blender 5.0. Also see #134029. Pull Request: https://projects.blender.org/blender/blender/pulls/143750
260 lines
8.9 KiB
C++
260 lines
8.9 KiB
C++
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "node_geometry_util.hh"
|
|
|
|
#include "ED_screen.hh"
|
|
|
|
#include "NOD_geo_bundle.hh"
|
|
#include "NOD_geometry_nodes_bundle.hh"
|
|
#include "NOD_socket_items_blend.hh"
|
|
#include "NOD_socket_items_ops.hh"
|
|
#include "NOD_socket_items_ui.hh"
|
|
#include "NOD_socket_search_link.hh"
|
|
#include "NOD_sync_sockets.hh"
|
|
|
|
#include "BKE_idprop.hh"
|
|
|
|
#include "BLO_read_write.hh"
|
|
|
|
#include "UI_interface_layout.hh"
|
|
|
|
#include <fmt/format.h>
|
|
|
|
namespace blender::nodes::node_geo_separate_bundle_cc {
|
|
|
|
NODE_STORAGE_FUNCS(NodeSeparateBundle);
|
|
|
|
static void node_declare(NodeDeclarationBuilder &b)
|
|
{
|
|
b.add_input<decl::Bundle>("Bundle");
|
|
const bNodeTree *tree = b.tree_or_null();
|
|
const bNode *node = b.node_or_null();
|
|
if (tree && node) {
|
|
const NodeSeparateBundle &storage = node_storage(*node);
|
|
for (const int i : IndexRange(storage.items_num)) {
|
|
const NodeSeparateBundleItem &item = storage.items[i];
|
|
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
|
|
const StringRef name = item.name ? item.name : "";
|
|
const std::string identifier = SeparateBundleItemsAccessor::socket_identifier_for_item(item);
|
|
b.add_output(socket_type, name, identifier)
|
|
.socket_name_ptr(&tree->id, SeparateBundleItemsAccessor::item_srna, &item, "name")
|
|
.propagate_all()
|
|
.reference_pass_all()
|
|
.structure_type(StructureType::Dynamic);
|
|
}
|
|
}
|
|
b.add_output<decl::Extend>("", "__extend__");
|
|
}
|
|
|
|
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
|
{
|
|
auto *storage = MEM_callocN<NodeSeparateBundle>(__func__);
|
|
node->storage = storage;
|
|
}
|
|
|
|
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
|
|
{
|
|
const NodeSeparateBundle &src_storage = node_storage(*src_node);
|
|
auto *dst_storage = MEM_dupallocN<NodeSeparateBundle>(__func__, src_storage);
|
|
dst_node->storage = dst_storage;
|
|
|
|
socket_items::copy_array<SeparateBundleItemsAccessor>(*src_node, *dst_node);
|
|
}
|
|
|
|
static void node_free_storage(bNode *node)
|
|
{
|
|
socket_items::destruct_array<SeparateBundleItemsAccessor>(*node);
|
|
MEM_freeN(node->storage);
|
|
}
|
|
|
|
static bool node_insert_link(bke::NodeInsertLinkParams ¶ms)
|
|
{
|
|
if (params.C && params.link.tonode == ¶ms.node && params.link.fromsock->type == SOCK_BUNDLE)
|
|
{
|
|
const NodeSeparateBundle &storage = node_storage(params.node);
|
|
if (storage.items_num == 0) {
|
|
SpaceNode *snode = CTX_wm_space_node(params.C);
|
|
if (snode && snode->edittree == ¶ms.ntree) {
|
|
sync_sockets_separate_bundle(*snode, params.node, nullptr, params.link.fromsock);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return socket_items::try_add_item_via_any_extend_socket<SeparateBundleItemsAccessor>(
|
|
params.ntree, params.node, params.node, params.link);
|
|
}
|
|
|
|
static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *node_ptr)
|
|
{
|
|
bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(node_ptr->owner_id);
|
|
bNode &node = *static_cast<bNode *>(node_ptr->data);
|
|
|
|
layout->op("node.sockets_sync", "Sync", ICON_FILE_REFRESH);
|
|
if (uiLayout *panel = layout->panel(C, "bundle_items", false, TIP_("Bundle Items"))) {
|
|
socket_items::ui::draw_items_list_with_operators<SeparateBundleItemsAccessor>(
|
|
C, panel, ntree, node);
|
|
socket_items::ui::draw_active_item_props<SeparateBundleItemsAccessor>(
|
|
ntree, node, [&](PointerRNA *item_ptr) {
|
|
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, "Type", ICON_NONE);
|
|
});
|
|
}
|
|
}
|
|
|
|
static void node_operators()
|
|
{
|
|
socket_items::ops::make_common_operators<SeparateBundleItemsAccessor>();
|
|
}
|
|
|
|
static void node_geo_exec(GeoNodeExecParams params)
|
|
{
|
|
nodes::BundlePtr bundle = params.extract_input<nodes::BundlePtr>("Bundle");
|
|
if (!bundle) {
|
|
params.set_default_remaining_outputs();
|
|
return;
|
|
}
|
|
|
|
const bNode &node = params.node();
|
|
const NodeSeparateBundle &storage = node_storage(node);
|
|
|
|
lf::Params &lf_params = params.low_level_lazy_function_params();
|
|
|
|
for (const int i : IndexRange(storage.items_num)) {
|
|
const NodeSeparateBundleItem &item = storage.items[i];
|
|
const StringRef name = item.name;
|
|
if (name.is_empty()) {
|
|
continue;
|
|
}
|
|
const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type);
|
|
if (!stype || !stype->geometry_nodes_cpp_type) {
|
|
continue;
|
|
}
|
|
const BundleItemValue *value = bundle->lookup(name);
|
|
if (!value) {
|
|
params.error_message_add(
|
|
NodeWarningType::Error,
|
|
fmt::format(fmt::runtime(TIP_("Value not found in bundle: \"{}\"")), name));
|
|
continue;
|
|
}
|
|
const auto *socket_value = std::get_if<BundleItemSocketValue>(&value->value);
|
|
if (!socket_value) {
|
|
params.error_message_add(
|
|
NodeWarningType::Error,
|
|
fmt::format("{}: \"{}\"", TIP_("Cannot get internal value from bundle"), name));
|
|
continue;
|
|
}
|
|
void *output_ptr = lf_params.get_output_data_ptr(i);
|
|
if (socket_value->type->type == stype->type) {
|
|
socket_value->type->geometry_nodes_cpp_type->copy_construct(socket_value->value, output_ptr);
|
|
}
|
|
else {
|
|
if (implicitly_convert_socket_value(
|
|
*socket_value->type, socket_value->value, *stype, output_ptr))
|
|
{
|
|
params.error_message_add(
|
|
NodeWarningType::Info,
|
|
fmt::format("{}: \"{}\" ({} " BLI_STR_UTF8_BLACK_RIGHT_POINTING_SMALL_TRIANGLE " {})",
|
|
TIP_("Implicit type conversion when separating bundle"),
|
|
name,
|
|
TIP_(socket_value->type->label),
|
|
TIP_(stype->label)));
|
|
}
|
|
else {
|
|
params.error_message_add(
|
|
NodeWarningType::Error,
|
|
fmt::format("{}: \"{}\" ({} " BLI_STR_UTF8_BLACK_RIGHT_POINTING_SMALL_TRIANGLE " {})",
|
|
TIP_("Conversion not supported when separating bundle"),
|
|
name,
|
|
TIP_(socket_value->type->label),
|
|
TIP_(stype->label)));
|
|
construct_socket_default_value(*stype, output_ptr);
|
|
}
|
|
}
|
|
lf_params.output_set(i);
|
|
}
|
|
|
|
params.set_default_remaining_outputs();
|
|
}
|
|
|
|
static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
|
{
|
|
const bNodeSocket &other_socket = params.other_socket();
|
|
if (other_socket.in_out == SOCK_IN) {
|
|
if (!SeparateBundleItemsAccessor::supports_socket_type(other_socket.typeinfo->type,
|
|
params.node_tree().type))
|
|
{
|
|
return;
|
|
}
|
|
params.add_item("Item", [](LinkSearchOpParams ¶ms) {
|
|
bNode &node = params.add_node("NodeSeparateBundle");
|
|
const auto *item =
|
|
socket_items::add_item_with_socket_type_and_name<SeparateBundleItemsAccessor>(
|
|
params.node_tree, node, params.socket.typeinfo->type, params.socket.name);
|
|
params.update_and_connect_available_socket(node, item->name);
|
|
});
|
|
}
|
|
else {
|
|
if (other_socket.type != SOCK_BUNDLE) {
|
|
return;
|
|
}
|
|
params.add_item("Bundle", [](LinkSearchOpParams ¶ms) {
|
|
bNode &node = params.add_node("NodeSeparateBundle");
|
|
params.connect_available_socket(node, "Bundle");
|
|
|
|
SpaceNode &snode = *CTX_wm_space_node(¶ms.C);
|
|
sync_sockets_separate_bundle(snode, node, nullptr);
|
|
});
|
|
}
|
|
}
|
|
|
|
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
|
|
{
|
|
socket_items::blend_write<SeparateBundleItemsAccessor>(&writer, node);
|
|
}
|
|
|
|
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
|
|
{
|
|
socket_items::blend_read_data<SeparateBundleItemsAccessor>(&reader, node);
|
|
}
|
|
|
|
static void node_register()
|
|
{
|
|
static blender::bke::bNodeType ntype;
|
|
|
|
geo_node_type_base(&ntype, "NodeSeparateBundle", NODE_SEPARATE_BUNDLE);
|
|
ntype.ui_name = "Separate Bundle";
|
|
ntype.ui_description = "Split a bundle into multiple sockets.";
|
|
ntype.nclass = NODE_CLASS_CONVERTER;
|
|
ntype.declare = node_declare;
|
|
ntype.initfunc = node_init;
|
|
ntype.insert_link = node_insert_link;
|
|
ntype.geometry_node_execute = node_geo_exec;
|
|
ntype.draw_buttons_ex = node_layout_ex;
|
|
ntype.gather_link_search_ops = node_gather_link_searches;
|
|
ntype.register_operators = node_operators;
|
|
ntype.blend_write_storage_content = node_blend_write;
|
|
ntype.blend_data_read_storage_content = node_blend_read;
|
|
bke::node_type_storage(ntype, "NodeSeparateBundle", node_free_storage, node_copy_storage);
|
|
blender::bke::node_register_type(ntype);
|
|
}
|
|
NOD_REGISTER_NODE(node_register)
|
|
|
|
} // namespace blender::nodes::node_geo_separate_bundle_cc
|
|
|
|
namespace blender::nodes {
|
|
|
|
StructRNA *SeparateBundleItemsAccessor::item_srna = &RNA_NodeSeparateBundleItem;
|
|
|
|
void SeparateBundleItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
|
|
{
|
|
BLO_write_string(writer, item.name);
|
|
}
|
|
|
|
void SeparateBundleItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
|
|
{
|
|
BLO_read_string(reader, &item.name);
|
|
}
|
|
|
|
} // namespace blender::nodes
|