WIP: Geometry Nodes: new For-Each zone #112446

Draft
Jacques Lucke wants to merge 51 commits from JacquesLucke/blender:for-each-zone into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
36 changed files with 1467 additions and 84 deletions

View File

@ -880,6 +880,7 @@ const bTheme U_theme_default = {
.nodeclass_attribute = RGBA(0x001566ff),
.node_zone_simulation = RGBA(0x66416233),
.node_zone_repeat = RGBA(0x76512f33),
.node_zone_foreach = RGBA(0x2f517633),
.movie = RGBA(0x0f0f0fcc),
.gp_vertex_size = 3,
.gp_vertex = RGBA(0x97979700),

View File

@ -458,6 +458,63 @@ class RepeatZoneItemMoveOperator(RepeatZoneOperator, ZoneMoveItemOperator, Opera
bl_options = {'REGISTER', 'UNDO'}
class ForEachZoneOperator(ZoneOperator):
input_node_type = 'GeometryNodeForEachInput'
output_node_type = 'GeometryNodeForEachOutput'
class ForEachInputOperator(ForEachZoneOperator):
items_name = "input_items"
active_index_name = "input_active_index"
class ForEachInputItemAddOperator(ForEachInputOperator, ZoneItemAddOperator, Operator):
"""Add an item to the for-each input node"""
bl_idname = "node.foreach_zone_input_item_add"
bl_label = "Add For-Each Input"
bl_options = {'REGISTER', 'UNDO'}
default_socket_type = 'FLOAT'
class ForEachInputItemRemoveOperator(ForEachInputOperator, ZoneItemRemoveOperator, Operator):
"""Remove an item from the for-each input node"""
bl_idname = "node.foreach_zone_input_item_remove"
bl_label = "Remove For-Each Input"
bl_options = {'REGISTER', 'UNDO'}
class ForEachInputItemMoveOperator(ForEachInputOperator, ZoneMoveItemOperator, Operator):
"""Move an item up or down in the list"""
bl_idname = "node.foreach_zone_input_item_move"
bl_label = "Move For-Each Input"
bl_options = {'REGISTER', 'UNDO'}
class ForEachOutputOperator(ForEachZoneOperator):
items_name = "output_items"
active_index_name = "output_active_index"
class ForEachOutputItemAddOperator(ForEachOutputOperator, ZoneItemAddOperator, Operator):
"""Add an item to the for-each output node"""
bl_idname = "node.foreach_zone_output_item_add"
bl_label = "Add For-Each Output"
bl_options = {'REGISTER', 'UNDO'}
class ForEachOutputItemRemoveOperator(ForEachOutputOperator, ZoneItemRemoveOperator, Operator):
"""Remove an item from the for-each output node"""
bl_idname = "node.foreach_zone_output_item_remove"
bl_label = "Remove For-Each Output"
bl_options = {'REGISTER', 'UNDO'}
class ForEachOutputItemMoveOperator(ForEachOutputOperator, ZoneMoveItemOperator, Operator):
"""Move an item up or down in the list"""
bl_idname = "node.foreach_zone_output_item_move"
bl_label = "Move For-Each Output"
bl_options = {'REGISTER', 'UNDO'}
classes = (
NewGeometryNodesModifier,
NewGeometryNodeTreeAssign,
@ -469,4 +526,10 @@ classes = (
RepeatZoneItemAddOperator,
RepeatZoneItemRemoveOperator,
RepeatZoneItemMoveOperator,
ForEachInputItemAddOperator,
ForEachInputItemRemoveOperator,
ForEachInputItemMoveOperator,
ForEachOutputItemAddOperator,
ForEachOutputItemRemoveOperator,
ForEachOutputItemMoveOperator,
)

View File

@ -182,9 +182,12 @@ class NodeAddZoneOperator(NodeAddOperator):
# Connect geometry sockets by default.
# Get the sockets by their types, because the name is not guaranteed due to i18n.
from_socket = next(s for s in input_node.outputs if s.type == 'GEOMETRY')
to_socket = next(s for s in output_node.inputs if s.type == 'GEOMETRY')
tree.links.new(to_socket, from_socket)
try:
from_socket = next(s for s in input_node.outputs if s.type == 'GEOMETRY')
to_socket = next(s for s in output_node.inputs if s.type == 'GEOMETRY')
tree.links.new(to_socket, from_socket)
except:
pass
return {'FINISHED'}
@ -209,6 +212,15 @@ class NODE_OT_add_repeat_zone(NodeAddZoneOperator, Operator):
output_node_type = "GeometryNodeRepeatOutput"
class NODE_OT_add_foreach_zone(NodeAddZoneOperator, Operator):
"""Add a for-each zone that allows executing nodes in parallel a dynamic number of times"""
bl_idname = "node.add_foreach_zone"
bl_label = "Add For-Each Zone"
bl_options = {'REGISTER', 'UNDO'}
input_node_type = "GeometryNodeForEachInput"
output_node_type = "GeometryNodeForEachOutput"
class NODE_OT_collapse_hide_unused_toggle(Operator):
"""Toggle collapsed nodes and hide unused sockets"""
bl_idname = "node.collapse_hide_unused_toggle"
@ -389,6 +401,7 @@ classes = (
NODE_OT_add_node,
NODE_OT_add_simulation_zone,
NODE_OT_add_repeat_zone,
NODE_OT_add_foreach_zone,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_interface_item_new,
NODE_OT_interface_item_duplicate,

View File

@ -74,6 +74,11 @@ def add_repeat_zone(layout, label):
props.use_transform = True
return props
def add_foreach_zone(layout, label):
props = layout.operator("node.add_foreach_zone", text=label, text_ctxt=i18n_contexts.default)
props.use_transform = True
return props
class NODE_MT_category_layout(Menu):
bl_idname = "NODE_MT_category_layout"

View File

@ -542,6 +542,7 @@ class NODE_MT_category_GEO_UTILITIES(Menu):
layout.menu("NODE_MT_category_GEO_UTILITIES_ROTATION")
layout.separator()
node_add_menu.add_node_type(layout, "FunctionNodeRandomValue")
node_add_menu.add_foreach_zone(layout, label="For-Each Zone")
node_add_menu.add_repeat_zone(layout, label="Repeat Zone")
node_add_menu.add_node_type(layout, "GeometryNodeSwitch")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)

View File

@ -1143,6 +1143,114 @@ class NODE_PT_repeat_zone_items(Panel):
layout.prop(output_node, "inspection_index")
class NODE_UL_foreach_zone_input_items(bpy.types.UIList):
def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index):
draw_socket_item_in_list(self, layout, item, icon)
class NODE_UL_foreach_zone_output_items(bpy.types.UIList):
def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index):
draw_socket_item_in_list(self, layout, item, icon)
class NODE_PT_foreach_zone_items(Panel):
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = "Node"
bl_label = "For-Each"
input_node_type = 'GeometryNodeForEachInput'
output_node_type = 'GeometryNodeForEachOutput'
@classmethod
def get_output_node(cls, context):
node = context.active_node
if node.bl_idname == cls.input_node_type:
return node.paired_output
if node.bl_idname == cls.output_node_type:
return node
return None
@classmethod
def poll(cls, context):
snode = context.space_data
if snode is None:
return False
node = context.active_node
if node is None or node.bl_idname not in (cls.input_node_type, cls.output_node_type):
return False
if cls.get_output_node(context) is None:
return False
return True
def draw(self, context):
layout = self.layout
output_node = self.get_output_node(context)
self.draw_input_items(layout, output_node)
self.draw_output_items(layout, output_node)
def draw_input_items(self, layout, output_node):
split = layout.row()
split.template_list(
"NODE_UL_foreach_zone_input_items",
"",
output_node,
"input_items",
output_node,
"input_active_index")
ops_col = split.column()
add_remove_col = ops_col.column(align=True)
add_remove_col.operator("node.foreach_zone_input_item_add", icon='ADD', text="")
add_remove_col.operator("node.foreach_zone_input_item_remove", icon='REMOVE', text="")
ops_col.separator()
up_down_col = ops_col.column(align=True)
props = up_down_col.operator("node.foreach_zone_input_item_move", icon='TRIA_UP', text="")
props.direction = 'UP'
props = up_down_col.operator("node.foreach_zone_input_item_move", icon='TRIA_DOWN', text="")
props.direction = 'DOWN'
active_item = output_node.input_active_item
if active_item is not None:
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(active_item, "socket_type")
def draw_output_items(self, layout, output_node):
split = layout.row()
split.template_list(
"NODE_UL_foreach_zone_output_items",
"",
output_node,
"output_items",
output_node,
"output_active_index")
ops_col = split.column()
add_remove_col = ops_col.column(align=True)
add_remove_col.operator("node.foreach_zone_output_item_add", icon='ADD', text="")
add_remove_col.operator("node.foreach_zone_output_item_remove", icon='REMOVE', text="")
ops_col.separator()
up_down_col = ops_col.column(align=True)
props = up_down_col.operator("node.foreach_zone_output_item_move", icon='TRIA_UP', text="")
props.direction = 'UP'
props = up_down_col.operator("node.foreach_zone_output_item_move", icon='TRIA_DOWN', text="")
props.direction = 'DOWN'
active_item = output_node.output_active_item
if active_item is not None:
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(active_item, "socket_type")
# Grease Pencil properties
class NODE_PT_annotation(AnnotationDataPanel, Panel):
bl_space_type = 'NODE_EDITOR'
@ -1212,6 +1320,9 @@ classes = (
NODE_PT_simulation_zone_items,
NODE_UL_repeat_zone_items,
NODE_PT_repeat_zone_items,
NODE_UL_foreach_zone_input_items,
NODE_UL_foreach_zone_output_items,
NODE_PT_foreach_zone_items,
NODE_PT_active_node_properties,
node_panel(EEVEE_MATERIAL_PT_settings),

View File

@ -93,6 +93,8 @@ class SPREADSHEET_HT_header(bpy.types.Header):
layout.label(text="Simulation Zone")
elif ctx.type == 'REPEAT_ZONE':
layout.label(text="Repeat Zone")
elif ctx.type == 'FOREACH_ZONE':
layout.label(text='For-Each Zone')
elif ctx.type == 'VIEWER_NODE':
layout.label(text=ctx.ui_name)

View File

@ -108,4 +108,29 @@ class RepeatZoneComputeContext : public ComputeContext {
void print_current_in_line(std::ostream &stream) const override;
};
class ForEachZoneComputeContext : public ComputeContext {
private:
static constexpr const char *s_static_type = "FOREACH_ZONE";
int32_t output_node_id_;
int index_;
public:
ForEachZoneComputeContext(const ComputeContext *parent, int32_t output_node_id, int index);
ForEachZoneComputeContext(const ComputeContext *parent, const bNode &node, int index);
int32_t output_node_id() const
{
return output_node_id_;
}
int index() const
{
return index_;
}
private:
void print_current_in_line(std::ostream &stream) const override;
};
} // namespace blender::bke

View File

@ -1309,6 +1309,8 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_TOOL_SET_FACE_SET 2113
#define GEO_NODE_POINTS_TO_CURVES 2114
#define GEO_NODE_INPUT_EDGE_SMOOTH 2115
#define GEO_NODE_FOR_EACH_INPUT 2116
#define GEO_NODE_FOR_EACH_OUTPUT 2117
/** \} */

View File

@ -37,6 +37,7 @@ extern "C" {
enum ViewerPathEqualFlag {
VIEWER_PATH_EQUAL_FLAG_IGNORE_REPEAT_ITERATION = (1 << 0),
VIEWER_PATH_EQUAL_FLAG_IGNORE_FOREACH_INDEX = (1 << 1),
};
void BKE_viewer_path_init(ViewerPath *viewer_path);
@ -57,6 +58,7 @@ GroupNodeViewerPathElem *BKE_viewer_path_elem_new_group_node(void);
SimulationZoneViewerPathElem *BKE_viewer_path_elem_new_simulation_zone(void);
ViewerNodeViewerPathElem *BKE_viewer_path_elem_new_viewer_node(void);
RepeatZoneViewerPathElem *BKE_viewer_path_elem_new_repeat_zone(void);
ForEachZoneViewerPathElem *BKE_viewer_path_elem_new_foreach_zone(void);
ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src);
bool BKE_viewer_path_elem_equal(const ViewerPathElem *a,
const ViewerPathElem *b,

View File

@ -119,4 +119,33 @@ void RepeatZoneComputeContext::print_current_in_line(std::ostream &stream) const
stream << "Repeat Zone ID: " << output_node_id_;
}
ForEachZoneComputeContext::ForEachZoneComputeContext(const ComputeContext *parent,
const int32_t output_node_id,
const int index)
: ComputeContext(s_static_type, parent), output_node_id_(output_node_id), index_(index)
{
/* Mix static type and node id into a single buffer so that only a single call to #mix_in is
* necessary. */
const int type_size = strlen(s_static_type);
const int buffer_size = type_size + 1 + sizeof(int32_t) + sizeof(int);
DynamicStackBuffer<64, 8> buffer_owner(buffer_size, 8);
char *buffer = static_cast<char *>(buffer_owner.buffer());
memcpy(buffer, s_static_type, type_size + 1);
memcpy(buffer + type_size + 1, &output_node_id_, sizeof(int32_t));
memcpy(buffer + type_size + 1 + sizeof(int32_t), &index_, sizeof(int));
hash_.mix_in(buffer, buffer_size);
}
ForEachZoneComputeContext::ForEachZoneComputeContext(const ComputeContext *parent,
const bNode &node,
const int index)
: ForEachZoneComputeContext(parent, node.identifier, index)
{
}
void ForEachZoneComputeContext::print_current_in_line(std::ostream &stream) const
{
stream << "Repeat Zone ID: " << output_node_id_;
}
} // namespace blender::bke

View File

@ -788,6 +788,10 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
if (node->type == GEO_NODE_REPEAT_OUTPUT) {
blender::nodes::RepeatItemsAccessor::blend_write(writer, *node);
}
if (node->type == GEO_NODE_FOR_EACH_OUTPUT) {
blender::nodes::ForEachInputItemsAccessor::blend_write(writer, *node);
blender::nodes::ForEachOutputItemsAccessor::blend_write(writer, *node);
}
}
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
@ -985,6 +989,11 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree)
blender::nodes::RepeatItemsAccessor::blend_read_data(reader, *node);
break;
}
case GEO_NODE_FOR_EACH_OUTPUT: {
blender::nodes::ForEachInputItemsAccessor::blend_read_data(reader, *node);
blender::nodes::ForEachOutputItemsAccessor::blend_read_data(reader, *node);
break;
}
default:
break;

View File

@ -984,15 +984,29 @@ class NodeTreeMainUpdater {
}
}
else {
bool all_available_inputs_computed = true;
Vector<const bNodeSocket *, 16> src_sockets;
for (const bNodeSocket *input_socket : node.input_sockets()) {
if (input_socket->is_available()) {
if (!hash_by_socket_id[input_socket->index_in_tree()].has_value()) {
sockets_to_check.push(input_socket);
all_available_inputs_computed = false;
src_sockets.append(input_socket);
}
}
if (all_zone_output_node_types().contains(node.type)) {
const bNodeZoneType &zone_type = *zone_type_by_node_type(node.type);
if (const bNode *zone_input_node = zone_type.get_corresponding_input(tree, node)) {
for (const bNodeSocket *input_socket : zone_input_node->input_sockets()) {
if (input_socket->is_available()) {
src_sockets.append(input_socket);
}
}
}
}
bool all_available_inputs_computed = true;
for (const bNodeSocket *socket : src_sockets) {
if (!hash_by_socket_id[socket->index_in_tree()].has_value()) {
sockets_to_check.push(socket);
all_available_inputs_computed = false;
}
}
if (!all_available_inputs_computed) {
continue;
}
@ -1015,10 +1029,10 @@ class NodeTreeMainUpdater {
}
else {
socket_hash = get_socket_ptr_hash(socket);
for (const bNodeSocket *input_socket : node.input_sockets()) {
if (input_socket->is_available()) {
const uint32_t input_socket_hash = *hash_by_socket_id[input_socket->index_in_tree()];
socket_hash = noise::hash(socket_hash, input_socket_hash);
for (const bNodeSocket *src_socket : src_sockets) {
if (src_socket->is_available()) {
const uint32_t src_socket_hash = *hash_by_socket_id[src_socket->index_in_tree()];
socket_hash = noise::hash(socket_hash, src_socket_hash);
}
}
@ -1096,6 +1110,21 @@ class NodeTreeMainUpdater {
}
}
}
if (all_zone_output_node_types().contains(node.type)) {
const bNodeZoneType &zone_type = *zone_type_by_node_type(node.type);
if (const bNode *zone_input_node = zone_type.get_corresponding_input(tree, node)) {
if (zone_input_node->runtime->changed_flag != NTREE_CHANGED_NOTHING) {
return true;
}
for (const bNodeSocket *input_socket : zone_input_node->input_sockets()) {
bool &pushed = pushed_by_socket_id[input_socket->index_in_tree()];
if (!pushed) {
sockets_to_check.push(input_socket);
pushed = true;
}
}
}
}
/* The Normal node has a special case, because the value stored in the first output socket
* is used as input in the node. */
if (node.type == SH_NODE_NORMAL && socket.index() == 1) {

View File

@ -95,6 +95,11 @@ void BKE_viewer_path_blend_write(BlendWriter *writer, const ViewerPath *viewer_p
BLO_write_struct(writer, RepeatZoneViewerPathElem, typed_elem);
break;
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: {
const auto *typed_elem = reinterpret_cast<ForEachZoneViewerPathElem *>(elem);
BLO_write_struct(writer, ForEachZoneViewerPathElem, typed_elem);
break;
}
}
BLO_write_string(writer, elem->ui_name);
}
@ -110,6 +115,7 @@ void BKE_viewer_path_blend_read_data(BlendDataReader *reader, ViewerPath *viewer
case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE:
case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE:
case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE:
case VIEWER_PATH_ELEM_TYPE_ID: {
break;
}
@ -135,7 +141,8 @@ void BKE_viewer_path_foreach_id(LibraryForeachIDData *data, ViewerPath *viewer_p
case VIEWER_PATH_ELEM_TYPE_GROUP_NODE:
case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE:
case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE:
case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: {
break;
}
}
@ -155,7 +162,8 @@ void BKE_viewer_path_id_remap(ViewerPath *viewer_path, const IDRemapper *mapping
case VIEWER_PATH_ELEM_TYPE_GROUP_NODE:
case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE:
case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE:
case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: {
break;
}
}
@ -190,6 +198,9 @@ ViewerPathElem *BKE_viewer_path_elem_new(const ViewerPathElemType type)
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
return &make_elem<RepeatZoneViewerPathElem>(type)->base;
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: {
return &make_elem<ForEachZoneViewerPathElem>(type)->base;
}
}
BLI_assert_unreachable();
return nullptr;
@ -230,6 +241,12 @@ RepeatZoneViewerPathElem *BKE_viewer_path_elem_new_repeat_zone()
BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE));
}
ForEachZoneViewerPathElem *BKE_viewer_path_elem_new_foreach_zone()
{
return reinterpret_cast<ForEachZoneViewerPathElem *>(
BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE));
}
ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src)
{
ViewerPathElem *dst = BKE_viewer_path_elem_new(ViewerPathElemType(src->type));
@ -276,6 +293,12 @@ ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src)
new_elem->iteration = old_elem->iteration;
break;
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: {
const auto *old_elem = reinterpret_cast<const ForEachZoneViewerPathElem *>(src);
auto *new_elem = reinterpret_cast<ForEachZoneViewerPathElem *>(dst);
new_elem->foreach_output_node_id = old_elem->foreach_output_node_id;
new_elem->index = old_elem->index;
}
}
return dst;
}
@ -320,6 +343,13 @@ bool BKE_viewer_path_elem_equal(const ViewerPathElem *a,
((flag & VIEWER_PATH_EQUAL_FLAG_IGNORE_REPEAT_ITERATION) != 0 ||
a_elem->iteration == b_elem->iteration);
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: {
const auto *a_elem = reinterpret_cast<const ForEachZoneViewerPathElem *>(a);
const auto *b_elem = reinterpret_cast<const ForEachZoneViewerPathElem *>(b);
return a_elem->foreach_output_node_id == b_elem->foreach_output_node_id &&
((flag & VIEWER_PATH_EQUAL_FLAG_IGNORE_FOREACH_INDEX) != 0 ||
a_elem->index == b_elem->index);
}
}
return false;
}
@ -331,7 +361,8 @@ void BKE_viewer_path_elem_free(ViewerPathElem *elem)
case VIEWER_PATH_ELEM_TYPE_GROUP_NODE:
case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE:
case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE:
case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: {
break;
}
case VIEWER_PATH_ELEM_TYPE_MODIFIER: {

View File

@ -140,6 +140,7 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme)
*/
{
/* Keep this block, even when empty. */
FROM_DEFAULT_V4_UCHAR(space_node.node_zone_foreach);
}
#undef FROM_DEFAULT_V4_UCHAR

View File

@ -190,6 +190,7 @@ enum ThemeColorID {
TH_NODE_ZONE_SIMULATION,
TH_NODE_ZONE_REPEAT,
TH_NODE_ZONE_FOR_EACH,
TH_SIMULATED_FRAMES,
TH_CONSOLE_OUTPUT,

View File

@ -666,6 +666,9 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid)
case TH_NODE_ZONE_REPEAT:
cp = ts->node_zone_repeat;
break;
case TH_NODE_ZONE_FOR_EACH:
cp = ts->node_zone_foreach;
break;
case TH_SIMULATED_FRAMES:
cp = ts->simulated_frames;
break;

View File

@ -44,6 +44,12 @@ static ViewerPathElem *viewer_path_elem_for_zone(const bNodeTreeZone &zone)
node_elem->iteration = storage.inspection_index;
return &node_elem->base;
}
case GEO_NODE_FOR_EACH_OUTPUT: {
ForEachZoneViewerPathElem *node_elem = BKE_viewer_path_elem_new_foreach_zone();
node_elem->foreach_output_node_id = zone.output_node->identifier;
node_elem->index = 0;
return &node_elem->base;
}
}
BLI_assert_unreachable();
return nullptr;
@ -253,7 +259,8 @@ std::optional<ViewerPathForGeometryNodesViewer> parse_geometry_nodes_viewer(
if (!ELEM(elem->type,
VIEWER_PATH_ELEM_TYPE_GROUP_NODE,
VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE,
VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE))
VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE,
VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE))
{
return std::nullopt;
}
@ -320,6 +327,19 @@ bool exists_geometry_nodes_viewer(const ViewerPathForGeometryNodesViewer &parsed
zone = next_zone;
break;
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: {
const auto &typed_elem = *reinterpret_cast<const ForEachZoneViewerPathElem *>(path_elem);
const bNodeTreeZone *next_zone = tree_zones->get_zone_by_node(
typed_elem.foreach_output_node_id);
if (next_zone == nullptr) {
return false;
}
if (next_zone->parent_zone != zone) {
return false;
}
zone = next_zone;
break;
}
case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: {
const auto &typed_elem = *reinterpret_cast<const GroupNodeViewerPathElem *>(path_elem);
const bNode *group_node = ngroup->node_by_id(typed_elem.node_id);
@ -484,6 +504,12 @@ bNode *find_geometry_nodes_viewer(const ViewerPath &viewer_path, SpaceNode &snod
elem.iteration);
return true;
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: {
const auto &elem = reinterpret_cast<const ForEachZoneViewerPathElem &>(elem_generic);
compute_context_builder.push<bke::ForEachZoneComputeContext>(elem.foreach_output_node_id,
elem.index);
return true;
}
}
return false;
}

View File

@ -1836,6 +1836,73 @@ typedef struct NodeGeometryRepeatOutput {
#endif
} NodeGeometryRepeatOutput;
typedef struct NodeForEachInputItem {
char *name;
/** #eNodeSocketDatatype. */
short socket_type;
char _pad[2];
/**
* Generated unique identifier for sockets which stays the same even when the item order or
* names change.
*/
int identifier;
} NodeForEachInputItem;
typedef struct NodeForEachOutputItem {
char *name;
/** #eNodeSocketDatatype. */
short socket_type;
char _pad[2];
/**
* Generated unique identifier for sockets which stays the same even when the item order or
* names change.
*/
int identifier;
} NodeForEachOutputItem;
typedef struct NodeGeometryForEachInput {
/** bNode.identifier of the corresponding output node. */
int output_node_id;
} NodeGeometryForEachInput;
typedef struct NodeGeometryForEachOutput {
NodeForEachInputItem *input_items;
int input_items_num;
int input_active_index;
int input_next_identifier;
char _pad1[4];
NodeForEachOutputItem *output_items;
int output_items_num;
int output_active_index;
int output_next_identifier;
/** #GeometryNodeForEachMode. */
uint8_t mode;
/** #eAttrDomain. */
uint8_t domain;
char _pad2[2];
#ifdef __cplusplus
blender::Span<NodeForEachInputItem> input_items_span() const
{
return {this->input_items, this->input_items_num};
}
blender::MutableSpan<NodeForEachInputItem> input_items_span()
{
return {this->input_items, this->input_items_num};
}
blender::Span<NodeForEachOutputItem> output_items_span() const
{
return {this->output_items, this->output_items_num};
}
blender::MutableSpan<NodeForEachOutputItem> output_items_span()
{
return {this->output_items, this->output_items_num};
}
#endif
} NodeGeometryForEachOutput;
typedef struct NodeGeometryDistributePointsInVolume {
/** #GeometryNodePointDistributeVolumeMode. */
uint8_t mode;
@ -2742,3 +2809,9 @@ typedef enum NodeCombSepColorMode {
NODE_COMBSEP_COLOR_HSV = 1,
NODE_COMBSEP_COLOR_HSL = 2,
} NodeCombSepColorMode;
typedef enum GeometryNodeForEachMode {
GEO_NODE_FOR_EACH_MODE_INDEX = 0,
GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT = 1,
GEO_NODE_FOR_EACH_MODE_INSTANCE = 2,
} GeometryNodeForEachMode;

View File

@ -349,7 +349,7 @@ typedef struct ThemeSpace {
unsigned char node_zone_simulation[4];
unsigned char node_zone_repeat[4];
unsigned char _pad9[4];
unsigned char node_zone_foreach[4];
unsigned char simulated_frames[4];
/** For sequence editor. */

View File

@ -16,6 +16,7 @@ typedef enum ViewerPathElemType {
VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE = 3,
VIEWER_PATH_ELEM_TYPE_VIEWER_NODE = 4,
VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE = 5,
VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE = 6,
} ViewerPathElemType;
typedef struct ViewerPathElem {
@ -56,6 +57,13 @@ typedef struct RepeatZoneViewerPathElem {
int iteration;
} RepeatZoneViewerPathElem;
typedef struct ForEachZoneViewerPathElem {
ViewerPathElem base;
int foreach_output_node_id;
int index;
} ForEachZoneViewerPathElem;
typedef struct ViewerNodeViewerPathElem {
ViewerPathElem base;

View File

@ -608,6 +608,8 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = {
# include "DNA_scene_types.h"
# include "WM_api.hh"
using blender::nodes::ForEachInputItemsAccessor;
using blender::nodes::ForEachOutputItemsAccessor;
using blender::nodes::RepeatItemsAccessor;
using blender::nodes::SimulationItemsAccessor;
@ -3267,14 +3269,20 @@ static void rna_Node_ItemArray_item_update(Main *bmain, Scene * /*scene*/, Point
template<typename Accessor>
static const EnumPropertyItem *rna_Node_ItemArray_socket_type_itemf(bContext * /*C*/,
PointerRNA * /*ptr*/,
PointerRNA *ptr,
PropertyRNA * /*prop*/,
bool *r_free)
{
using ItemT = typename Accessor::ItemT;
bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
ItemT &item = *static_cast<ItemT *>(ptr->data);
const bNode *node = blender::nodes::socket_items::find_node_by_item<Accessor>(ntree, item);
BLI_assert(node != nullptr);
*r_free = true;
return itemf_function_check(
rna_enum_node_socket_data_type_items, [](const EnumPropertyItem *item) {
return Accessor::supports_socket_type(eNodeSocketDatatype(item->value));
rna_enum_node_socket_data_type_items, [&](const EnumPropertyItem *item) {
return Accessor::supports_socket_type(*node, eNodeSocketDatatype(item->value));
});
}
@ -3303,7 +3311,7 @@ typename Accessor::ItemT *rna_Node_ItemArray_new_with_socket_and_name(
ID *id, bNode *node, Main *bmain, ReportList *reports, int socket_type, const char *name)
{
using ItemT = typename Accessor::ItemT;
if (!Accessor::supports_socket_type(eNodeSocketDatatype(socket_type))) {
if (!Accessor::supports_socket_type(*node, eNodeSocketDatatype(socket_type))) {
BKE_report(reports, RPT_ERROR, "Unable to create item with this socket type");
return nullptr;
}
@ -3318,6 +3326,18 @@ typename Accessor::ItemT *rna_Node_ItemArray_new_with_socket_and_name(
return new_item;
}
static void rna_ForEachOutputNode_mode_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr)
{
bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode &node = *static_cast<bNode *>(ptr->data);
blender::nodes::socket_items::remove_unsupported_socket_types<ForEachInputItemsAccessor>(node);
blender::nodes::socket_items::remove_unsupported_socket_types<ForEachOutputItemsAccessor>(node);
BKE_ntree_update_tag_node_property(&ntree, &node);
ED_node_tree_propagate_change(nullptr, bmain, &ntree);
}
/* ******** Node Socket Types ******** */
static PointerRNA rna_NodeOutputFile_slot_layer_get(CollectionPropertyIterator *iter)
@ -8725,22 +8745,29 @@ static void def_geo_repeat_input(StructRNA *srna)
def_common_zone_input(srna);
}
static void def_geo_foreach_input(StructRNA *srna)
{
RNA_def_struct_sdna_from(srna, "NodeGeometryForEachInput", "storage");
def_common_zone_input(srna);
}
static void rna_def_node_item_array_socket_item_common(StructRNA *srna, const char *accessor)
{
static blender::LinearAllocator<> allocator;
PropertyRNA *prop;
char name_set_func[64];
char name_set_func[128];
SNPRINTF(name_set_func, "rna_Node_ItemArray_item_name_set<%s>", accessor);
char item_update_func[64];
char item_update_func[128];
SNPRINTF(item_update_func, "rna_Node_ItemArray_item_update<%s>", accessor);
const char *item_update_func_ptr = allocator.copy_string(item_update_func).c_str();
char socket_type_itemf[64];
char socket_type_itemf[128];
SNPRINTF(socket_type_itemf, "rna_Node_ItemArray_socket_type_itemf<%s>", accessor);
char color_get_func[64];
char color_get_func[128];
SNPRINTF(color_get_func, "rna_Node_ItemArray_item_color_get<%s>", accessor);
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
@ -8775,11 +8802,11 @@ static void rna_def_node_item_array_common_functions(StructRNA *srna,
PropertyRNA *parm;
FunctionRNA *func;
char remove_call[64];
char remove_call[128];
SNPRINTF(remove_call, "rna_Node_ItemArray_remove<%s>", accessor_name);
char clear_call[64];
char clear_call[128];
SNPRINTF(clear_call, "rna_Node_ItemArray_clear<%s>", accessor_name);
char move_call[64];
char move_call[128];
SNPRINTF(move_call, "rna_Node_ItemArray_move<%s>", accessor_name);
func = RNA_def_function(srna, "remove", allocator.copy_string(remove_call).c_str());
@ -8956,6 +8983,127 @@ static void def_geo_repeat_output(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE, "rna_Node_update");
}
static void rna_def_foreach_input_item(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "ForEachInputItem", nullptr);
RNA_def_struct_ui_text(srna, "For-Each Input Item", "");
RNA_def_struct_sdna(srna, "NodeForEachInputItem");
rna_def_node_item_array_socket_item_common(srna, "ForEachInputItemsAccessor");
}
static void rna_def_foreach_output_item(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "ForEachOutputItem", nullptr);
RNA_def_struct_ui_text(srna, "For-Each Output Item", "");
RNA_def_struct_sdna(srna, "NodeForEachOutputItem");
rna_def_node_item_array_socket_item_common(srna, "ForEachOutputItemsAccessor");
}
static void rna_def_geo_foreach_input_items(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometryForEachInputItems", nullptr);
RNA_def_struct_sdna(srna, "bNode");
RNA_def_struct_ui_text(srna, "Input Items", "Collection of for-each input items");
rna_def_node_item_array_new_with_socket_and_name(
srna, "ForEachInputItem", "ForEachInputItemsAccessor");
rna_def_node_item_array_common_functions(srna, "ForEachInputItem", "ForEachInputItemsAccessor");
}
static void rna_def_geo_foreach_output_items(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometryForEachOutputItems", nullptr);
RNA_def_struct_sdna(srna, "bNode");
RNA_def_struct_ui_text(srna, "Output Items", "Collection of for-each output items");
rna_def_node_item_array_new_with_socket_and_name(
srna, "ForEachOutputItem", "ForEachOutputItemsAccessor");
rna_def_node_item_array_common_functions(
srna, "ForEachOutputItem", "ForEachOutputItemsAccessor");
}
static void def_geo_foreach_output(StructRNA *srna)
{
PropertyRNA *prop;
static const EnumPropertyItem mode_items[] = {
{GEO_NODE_FOR_EACH_MODE_INDEX, "INDEX", 0, "Index", "Do something for each index"},
{GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT,
"GEOMETRY_ELEMENT",
0,
"Geometry Element",
"Do something for each element in a geometry on a specific domain"},
{GEO_NODE_FOR_EACH_MODE_INSTANCE,
"INSTANCE",
0,
"Instance",
"Do something for each unique top level instance"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_struct_sdna_from(srna, "NodeGeometryForEachOutput", "storage");
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, mode_items);
RNA_def_property_ui_text(prop, "Mode", "");
RNA_def_property_update(prop, NC_NODE, "rna_ForEachOutputNode_mode_update");
prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items);
RNA_def_property_ui_text(prop, "Domain", "");
RNA_def_property_update(prop, NC_NODE, "rna_Node_update");
prop = RNA_def_property(srna, "input_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "input_items", "input_items_num");
RNA_def_property_struct_type(prop, "ForEachInputItem");
RNA_def_property_ui_text(prop, "Input Items", "");
RNA_def_property_srna(prop, "NodeGeometryForEachInputItems");
prop = RNA_def_property(srna, "input_active_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "input_active_index");
RNA_def_property_ui_text(prop, "Active Input Item Index", "Index of the active item");
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, NC_NODE, nullptr);
prop = RNA_def_property(srna, "input_active_item", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "ForEachInputItem");
RNA_def_property_pointer_funcs(prop,
"rna_Node_ItemArray_active_get<ForEachInputItemsAccessor>",
"rna_Node_ItemArray_active_set<ForEachInputItemsAccessor>",
nullptr,
nullptr);
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_NO_DEG_UPDATE);
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
RNA_def_property_update(prop, NC_NODE, nullptr);
prop = RNA_def_property(srna, "output_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "output_items", "output_items_num");
RNA_def_property_struct_type(prop, "ForEachOutputItem");
RNA_def_property_ui_text(prop, "OutputItems", "");
RNA_def_property_srna(prop, "NodeGeometryForEachOutputItems");
prop = RNA_def_property(srna, "output_active_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "output_active_index");
RNA_def_property_ui_text(prop, "Active Output Item Index", "Index of the active item");
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, NC_NODE, nullptr);
prop = RNA_def_property(srna, "output_active_item", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "ForEachOutputItem");
RNA_def_property_pointer_funcs(prop,
"rna_Node_ItemArray_active_get<ForEachOutputItemsAccessor>",
"rna_Node_ItemArray_active_set<ForEachOutputItemsAccessor>",
nullptr,
nullptr);
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_NO_DEG_UPDATE);
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
RNA_def_property_update(prop, NC_NODE, nullptr);
}
static void def_geo_curve_handle_type_selection(StructRNA *srna)
{
PropertyRNA *prop;
@ -10278,6 +10426,8 @@ void RNA_def_nodetree(BlenderRNA *brna)
rna_def_simulation_state_item(brna);
rna_def_repeat_item(brna);
rna_def_foreach_input_item(brna);
rna_def_foreach_output_item(brna);
# define DefNode(Category, ID, DefFunc, EnumName, StructName, UIName, UIDesc) \
{ \
@ -10330,6 +10480,8 @@ void RNA_def_nodetree(BlenderRNA *brna)
rna_def_cmp_output_file_slot_layer(brna);
rna_def_geo_simulation_output_items(brna);
rna_def_geo_repeat_output_items(brna);
rna_def_geo_foreach_input_items(brna);
rna_def_geo_foreach_output_items(brna);
rna_def_node_instance_hash(brna);
}

View File

@ -3348,6 +3348,8 @@ static StructRNA *rna_viewer_path_elem_refine(PointerRNA *ptr)
return &RNA_ViewerNodeViewerPathElem;
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE:
return &RNA_RepeatZoneViewerPathElem;
case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE:
return &RNA_ForEachZoneViewerPathElem;
}
BLI_assert_unreachable();
return nullptr;
@ -8090,6 +8092,7 @@ static const EnumPropertyItem viewer_path_elem_type_items[] = {
{VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE, "SIMULATION_ZONE", ICON_NONE, "Simulation Zone", ""},
{VIEWER_PATH_ELEM_TYPE_VIEWER_NODE, "VIEWER_NODE", ICON_NONE, "Viewer Node", ""},
{VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE, "REPEAT_ZONE", ICON_NONE, "Repeat", ""},
{VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE, "FOREACH_ZONE", ICON_NONE, "For-Each Zone", ""},
{0, nullptr, 0, nullptr, nullptr},
};
@ -8168,6 +8171,17 @@ static void rna_def_repeat_zone_viewer_path_elem(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Repeat Output Node ID", "");
}
static void rna_def_foreach_zone_viewer_path_elem(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "ForEachZoneViewerPathElem", "ViewerPathElem");
prop = RNA_def_property(srna, "foreach_output_node_id", PROP_INT, PROP_NONE);
RNA_def_property_ui_text(prop, "For-Each Output Node ID", "");
}
static void rna_def_viewer_node_viewer_path_elem(BlenderRNA *brna)
{
StructRNA *srna;
@ -8190,6 +8204,7 @@ static void rna_def_viewer_path(BlenderRNA *brna)
rna_def_group_node_viewer_path_elem(brna);
rna_def_simulation_zone_viewer_path_elem(brna);
rna_def_repeat_zone_viewer_path_elem(brna);
rna_def_foreach_zone_viewer_path_elem(brna);
rna_def_viewer_node_viewer_path_elem(brna);
srna = RNA_def_struct(brna, "ViewerPath", nullptr);

View File

@ -3208,6 +3208,12 @@ static void rna_def_userdef_theme_space_node(BlenderRNA *brna)
RNA_def_property_array(prop, 4);
RNA_def_property_ui_text(prop, "Repeat Zone", "");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
prop = RNA_def_property(srna, "foreach_zone", PROP_FLOAT, PROP_COLOR_GAMMA);
RNA_def_property_float_sdna(prop, nullptr, "node_zone_foreach");
RNA_def_property_array(prop, 4);
RNA_def_property_ui_text(prop, "For-Each Zone", "");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
}
static void rna_def_userdef_theme_space_buts(BlenderRNA *brna)

View File

@ -606,6 +606,28 @@ static void try_add_side_effect_node(const ComputeContext &final_compute_context
compute_context->iteration());
current_zone = repeat_zone;
}
else if (const auto *compute_context = dynamic_cast<const bke::ForEachZoneComputeContext *>(
compute_context_generic))
{
const bke::bNodeTreeZone *foreach_zone = current_zones->get_zone_by_node(
compute_context->output_node_id());
if (foreach_zone == nullptr) {
return;
}
if (foreach_zone->parent_zone != current_zone) {
return;
}
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
foreach_zone, nullptr);
if (lf_zone_node == nullptr) {
return;
}
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
local_side_effect_nodes.indices_by_foreach_zone.add(
{parent_compute_context_hash, compute_context->output_node_id()},
compute_context->index());
current_zone = foreach_zone;
}
else if (const auto *compute_context = dynamic_cast<const bke::NodeGroupComputeContext *>(
compute_context_generic))
{

View File

@ -135,6 +135,7 @@ struct GeoNodesSideEffectNodes {
* repeat output node.
*/
MultiValueMap<std::pair<ComputeContextHash, int32_t>, int> iterations_by_repeat_zone;
MultiValueMap<std::pair<ComputeContextHash, int32_t>, int> indices_by_foreach_zone;
};
/**

View File

@ -137,6 +137,56 @@ inline void move_item(T *items, const int items_num, const int from_index, const
}
}
/**
* Low level utility to remove all socket items for which the predicate is true.
*/
template<typename T>
inline void remove_if(T **items,
int *items_num,
int *active_index,
void (*destruct_item)(T *),
const FunctionRef<bool(const T &item)> predicate)
{
static_assert(std::is_trivial_v<T>);
T *old_items = *items;
const int old_items_num = *items_num;
Vector<int> indices_to_keep;
for (const int i : IndexRange(old_items_num)) {
T &item = old_items[i];
if (predicate(item)) {
destruct_item(&item);
}
else {
indices_to_keep.append(i);
}
}
const int new_items_num = indices_to_keep.size();
if (old_items_num == new_items_num) {
return;
}
T *new_items = MEM_cnew_array<T>(new_items_num, __func__);
for (const int i : IndexRange(new_items_num)) {
new_items[i] = old_items[indices_to_keep[i]];
}
MEM_SAFE_FREE(old_items);
*items = new_items;
*items_num = new_items_num;
*active_index = std::max(0, std::min(*active_index, new_items_num - 1));
}
template<typename Accessor> inline void remove_unsupported_socket_types(bNode &node)
{
using ItemT = typename Accessor::ItemT;
SocketItemsRef ref = Accessor::get_items_from_node(node);
remove_if<ItemT>(
ref.items, ref.items_num, ref.active_index, Accessor::destruct_item, [&](const ItemT &item) {
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(
*Accessor::get_socket_type(const_cast<ItemT &>(item)));
return !Accessor::supports_socket_type(node, socket_type);
});
}
/**
* Destruct all the items and the free the array itself.
*/
@ -217,7 +267,7 @@ inline typename Accessor::ItemT *add_item_with_socket_and_name(
bNode &node, const eNodeSocketDatatype socket_type, const char *name)
{
using ItemT = typename Accessor::ItemT;
BLI_assert(Accessor::supports_socket_type(socket_type));
BLI_assert(Accessor::supports_socket_type(node, socket_type));
SocketItemsRef array = Accessor::get_items_from_node(node);
@ -263,7 +313,7 @@ template<typename Accessor>
return false;
}
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(src_socket->type);
if (!Accessor::supports_socket_type(socket_type)) {
if (!Accessor::supports_socket_type(storage_node, socket_type)) {
return false;
}
const ItemT *item = add_item_with_socket_and_name<Accessor>(

View File

@ -331,6 +331,8 @@ DefNode(GeometryNode, GEO_NODE_EXTRUDE_MESH, 0, "EXTRUDE_MESH", ExtrudeMesh, "Ex
DefNode(GeometryNode, GEO_NODE_FILL_CURVE, 0, "FILL_CURVE", FillCurve, "Fill Curve", "Generate a mesh on the XY plane with faces on the inside of input curves")
DefNode(GeometryNode, GEO_NODE_FILLET_CURVE, 0, "FILLET_CURVE", FilletCurve, "Fillet Curve", "Round corners by generating circular arcs on each control point")
DefNode(GeometryNode, GEO_NODE_FLIP_FACES, 0, "FLIP_FACES", FlipFaces, "Flip Faces", "Reverse the order of the vertices and edges of selected faces, flipping their normal direction")
DefNode(GeometryNode, GEO_NODE_FOR_EACH_INPUT, def_geo_foreach_input, "FOREACH_INPUT", ForEachInput, "For-Each Input", "")
DefNode(GeometryNode, GEO_NODE_FOR_EACH_OUTPUT, def_geo_foreach_output, "FOREACH_OUTPUT", ForEachOutput, "For-Each Output", "")
DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE", GeometryToInstance, "Geometry to Instance", "Convert each input geometry into an instance, which can be much faster than the Join Geometry node when the inputs are large")
DefNode(GeometryNode, GEO_NODE_IMAGE_INFO, 0, "IMAGE_INFO", ImageInfo, "Image Info", "Retrieve information about an image")
DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture")

View File

@ -47,7 +47,7 @@ struct SimulationItemsAccessor {
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
static bool supports_socket_type(const bNode & /*node*/, const eNodeSocketDatatype socket_type)
{
return ELEM(socket_type,
SOCK_FLOAT,
@ -109,7 +109,7 @@ struct RepeatItemsAccessor {
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
static bool supports_socket_type(const bNode & /*node*/, const eNodeSocketDatatype socket_type)
{
return ELEM(socket_type,
SOCK_FLOAT,
@ -141,4 +141,123 @@ struct RepeatItemsAccessor {
}
};
struct ForEachInputItemsAccessor {
using ItemT = NodeForEachInputItem;
static StructRNA *item_srna;
/* This refers to the node that stores the item array. */
static int node_type;
static constexpr const char *node_idname = "GeometryNodeForEachOutput";
static socket_items::SocketItemsRef<NodeForEachInputItem> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryForEachOutput *>(node.storage);
return {&storage->input_items, &storage->input_items_num, &storage->input_active_index};
}
static void copy_item(const NodeForEachInputItem &src, NodeForEachInputItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(NodeForEachInputItem *item)
{
MEM_SAFE_FREE(item->name);
}
static void blend_write(BlendWriter *, const bNode &node);
static void blend_read_data(BlendDataReader *reader, bNode &node);
static short *get_socket_type(NodeForEachInputItem &item)
{
return &item.socket_type;
}
static char **get_name(NodeForEachInputItem &item)
{
return &item.name;
}
static bool supports_socket_type(const bNode &node, const eNodeSocketDatatype socket_type)
{
auto *storage = static_cast<NodeGeometryForEachOutput *>(node.storage);
switch (GeometryNodeForEachMode(storage->mode)) {
case GEO_NODE_FOR_EACH_MODE_INDEX:
case GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT: {
return socket_type_supports_fields(socket_type);
}
case GEO_NODE_FOR_EACH_MODE_INSTANCE: {
return false;
}
}
return false;
}
static void init_with_socket_type_and_name(bNode &node,
NodeForEachInputItem &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryForEachOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->input_next_identifier++;
socket_items::set_item_name_and_make_unique<ForEachInputItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const NodeForEachInputItem &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
struct ForEachOutputItemsAccessor {
using ItemT = NodeForEachOutputItem;
static StructRNA *item_srna;
static int node_type;
static constexpr const char *node_idname = "GeometryNodeForEachOutput";
static socket_items::SocketItemsRef<NodeForEachOutputItem> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryForEachOutput *>(node.storage);
return {&storage->output_items, &storage->output_items_num, &storage->output_active_index};
}
static void copy_item(const NodeForEachOutputItem &src, NodeForEachOutputItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(NodeForEachOutputItem *item)
{
MEM_SAFE_FREE(item->name);
}
static void blend_write(BlendWriter *, const bNode &node);
static void blend_read_data(BlendDataReader *reader, bNode &node);
static short *get_socket_type(NodeForEachOutputItem &item)
{
return &item.socket_type;
}
static char **get_name(NodeForEachOutputItem &item)
{
return &item.name;
}
static bool supports_socket_type(const bNode &node, const eNodeSocketDatatype socket_type)
{
auto *storage = static_cast<NodeGeometryForEachOutput *>(node.storage);
switch (GeometryNodeForEachMode(storage->mode)) {
case GEO_NODE_FOR_EACH_MODE_INDEX:
case GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT:
case GEO_NODE_FOR_EACH_MODE_INSTANCE: {
return socket_type == SOCK_GEOMETRY || socket_type_supports_fields(socket_type);
}
}
return false;
}
static void init_with_socket_type_and_name(bNode &node,
NodeForEachOutputItem &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryForEachOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->output_next_identifier++;
socket_items::set_item_name_and_make_unique<ForEachOutputItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const NodeForEachOutputItem &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@ -77,6 +77,8 @@ set(SRC
nodes/node_geo_evaluate_on_domain.cc
nodes/node_geo_extrude_mesh.cc
nodes/node_geo_flip_faces.cc
nodes/node_geo_foreach_input.cc
nodes/node_geo_foreach_output.cc
nodes/node_geo_geometry_to_instance.cc
nodes/node_geo_image.cc
nodes/node_geo_image_info.cc

View File

@ -20,7 +20,6 @@
#endif
struct BVHTreeFromMesh;
struct GeometrySet;
namespace blender::nodes {
class GatherAddNodeSearchParams;
class GatherLinkSearchOpParams;

View File

@ -0,0 +1,138 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_compute_contexts.hh"
#include "BKE_scene.h"
#include "DEG_depsgraph_query.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "NOD_geometry.hh"
#include "NOD_socket.hh"
#include "NOD_zone_socket_items.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.h"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_foreach_input_cc {
NODE_STORAGE_FUNCS(NodeGeometryForEachInput);
static void node_declare_dynamic(const bNodeTree &tree,
const bNode &node,
NodeDeclarationBuilder &b)
{
const NodeGeometryForEachInput &input_storage = node_storage(node);
const bNode *output_node = tree.node_by_id(input_storage.output_node_id);
if (output_node == nullptr) {
return;
}
const auto &output_storage = *static_cast<const NodeGeometryForEachOutput *>(
output_node->storage);
const GeometryNodeForEachMode mode = GeometryNodeForEachMode(output_storage.mode);
{
/* Add standard inputs. */
switch (mode) {
case GEO_NODE_FOR_EACH_MODE_INDEX: {
b.add_input<decl::Int>("Amount").min(0).default_value(1);
b.add_output<decl::Int>("Index");
break;
}
case GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT: {
b.add_input<decl::Geometry>("Geometry");
b.add_input<decl::Bool>("Selection").default_value(true).hide_value(true).supports_field();
b.add_output<decl::Int>("Index");
if (output_storage.domain != ATTR_DOMAIN_CORNER) {
b.add_output<decl::Geometry>("Element");
}
break;
}
case GEO_NODE_FOR_EACH_MODE_INSTANCE: {
b.add_input<decl::Geometry>("Instances");
b.add_output<decl::Geometry>("Geometry");
break;
}
}
}
if (mode == GEO_NODE_FOR_EACH_MODE_INSTANCE) {
/* Other inputs are not allowed in this mode. */
return;
}
/* Add dynamic sockets. */
for (const int i : IndexRange(output_storage.input_items_num)) {
const NodeForEachInputItem &item = output_storage.input_items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const StringRef name = item.name;
const std::string identifier = ForEachInputItemsAccessor::socket_identifier_for_item(item);
auto &input_decl = b.add_input(socket_type, name, identifier);
b.add_output(socket_type, name, identifier);
if (socket_type_supports_fields(socket_type)) {
input_decl.supports_field();
}
}
b.add_input<decl::Extend>("", "__extend__");
b.add_output<decl::Extend>("", "__extend__");
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryForEachInput *data = MEM_cnew<NodeGeometryForEachInput>(__func__);
/* Needs to be initialized for the node to work. */
data->output_node_id = 0;
node->storage = data;
}
static bool node_insert_link(bNodeTree *tree, bNode *node, bNodeLink *link)
{
const NodeGeometryForEachInput &input_storage = node_storage(*node);
bNode *output_node = tree->node_by_id(input_storage.output_node_id);
if (output_node == nullptr) {
return true;
}
return socket_items::try_add_item_via_any_extend_socket<ForEachInputItemsAccessor>(
*tree, *node, *output_node, *link);
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode &input_node = *static_cast<bNode *>(ptr->data);
const NodeGeometryForEachInput &input_storage = node_storage(input_node);
bNode *output_node = tree.node_by_id(input_storage.output_node_id);
if (output_node == nullptr) {
return;
}
const auto &output_storage = *static_cast<const NodeGeometryForEachOutput *>(
output_node->storage);
PointerRNA output_node_ptr = RNA_pointer_create(ptr->owner_id, &RNA_Node, output_node);
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiLayout *col = uiLayoutColumn(layout, false);
uiItemR(col, &output_node_ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
if (output_storage.mode == GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT) {
uiItemR(col, &output_node_ptr, "domain", UI_ITEM_NONE, "Domain", ICON_NONE);
}
}
static void node_register()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_FOR_EACH_INPUT, "For-Each Input", NODE_CLASS_INTERFACE);
ntype.initfunc = node_init;
ntype.declare_dynamic = node_declare_dynamic;
ntype.gather_link_search_ops = nullptr;
ntype.insert_link = node_insert_link;
ntype.draw_buttons = node_layout;
node_type_storage(
&ntype, "NodeGeometryForEachInput", node_free_standard_storage, node_copy_standard_storage);
nodeRegisterType(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_foreach_input_cc

View File

@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_compute_contexts.hh"
#include "BKE_scene.h"
#include "DEG_depsgraph_query.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "NOD_geometry.hh"
#include "NOD_socket.hh"
#include "NOD_zone_socket_items.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_foreach_output_cc {
NODE_STORAGE_FUNCS(NodeGeometryForEachOutput);
static void node_declare_dynamic(const bNodeTree & /*node_tree*/,
const bNode &node,
NodeDeclarationBuilder &b)
{
const NodeGeometryForEachOutput &storage = node_storage(node);
for (const int i : IndexRange(storage.output_items_num)) {
const NodeForEachOutputItem &item = storage.output_items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const std::string identifier = ForEachOutputItemsAccessor::socket_identifier_for_item(item);
const StringRef name = item.name;
auto &input_decl = b.add_input(socket_type, name, identifier);
auto &output_decl = b.add_output(socket_type, name, identifier);
if (socket_type_supports_fields(socket_type)) {
input_decl.supports_field();
output_decl.field_on_all();
}
}
b.add_input<decl::Extend>("", "__extend__");
b.add_output<decl::Extend>("", "__extend__");
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryForEachOutput *data = MEM_cnew<NodeGeometryForEachOutput>(__func__);
data->output_items = MEM_cnew_array<NodeForEachOutputItem>(1, __func__);
data->output_items_num = 1;
NodeForEachOutputItem &item = data->output_items[0];
item.name = BLI_strdup(DATA_("Geometry"));
item.socket_type = SOCK_GEOMETRY;
item.identifier = data->output_next_identifier++;
node->storage = data;
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<ForEachInputItemsAccessor>(*node);
socket_items::destruct_array<ForEachOutputItemsAccessor>(*node);
MEM_freeN(node->storage);
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeGeometryForEachOutput &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_new<NodeGeometryForEachOutput>(__func__, src_storage);
dst_node->storage = dst_storage;
socket_items::copy_array<ForEachInputItemsAccessor>(*src_node, *dst_node);
socket_items::copy_array<ForEachOutputItemsAccessor>(*src_node, *dst_node);
}
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
{
return socket_items::try_add_item_via_any_extend_socket<ForEachOutputItemsAccessor>(
*ntree, *node, *node, *link);
}
static void node_register()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_FOR_EACH_OUTPUT, "For-Each Output", NODE_CLASS_INTERFACE);
ntype.initfunc = node_init;
ntype.declare_dynamic = node_declare_dynamic;
ntype.gather_link_search_ops = nullptr;
ntype.insert_link = node_insert_link;
node_type_storage(&ntype, "NodeGeometryForEachOutput", node_free_storage, node_copy_storage);
nodeRegisterType(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_foreach_output_cc

View File

@ -37,6 +37,8 @@
#include "DNA_ID.h"
#include "GEO_join_geometries.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_geometry_set.hh"
#include "BKE_node_tree_anonymous_attributes.hh"
@ -1483,6 +1485,67 @@ struct RepeatEvalStorage {
Vector<int> output_index_map;
};
static void build_interface_for_zone_function(const bNodeTreeZone &zone,
const ZoneBodyFunction &body_fn,
ZoneBuildInfo &r_zone_info,
Vector<lf::Input> &r_inputs,
Vector<lf::Output> &r_outputs)
{
for (const bNodeSocket *socket : zone.input_node->input_sockets()) {
if (socket->typeinfo->geometry_nodes_cpp_type == nullptr) {
/* Skip "empty" sockets. */
continue;
}
r_inputs.append_as(
socket->name, *socket->typeinfo->geometry_nodes_cpp_type, lf::ValueUsage::Maybe);
}
r_zone_info.indices.inputs.main = r_inputs.index_range();
for (const bNodeLink *link : zone.border_links) {
r_inputs.append_as(link->fromsock->name,
*link->tosock->typeinfo->geometry_nodes_cpp_type,
lf::ValueUsage::Maybe);
}
r_zone_info.indices.inputs.border_links = r_inputs.index_range().take_back(
zone.border_links.size());
for (const bNodeSocket *socket : zone.output_node->output_sockets()) {
if (socket->typeinfo->geometry_nodes_cpp_type == nullptr) {
continue;
}
r_inputs.append_as("Usage", CPPType::get<bool>(), lf::ValueUsage::Maybe);
r_outputs.append_as(socket->name, *socket->typeinfo->geometry_nodes_cpp_type);
}
r_zone_info.indices.outputs.main = r_outputs.index_range();
r_zone_info.indices.inputs.output_usages = r_inputs.index_range().take_back(
r_zone_info.indices.outputs.main.size());
for ([[maybe_unused]] const bNodeSocket *socket : zone.input_node->input_sockets()) {
if (socket->typeinfo->geometry_nodes_cpp_type == nullptr) {
continue;
}
r_outputs.append_as("Usage", CPPType::get<bool>());
}
r_zone_info.indices.outputs.input_usages = r_outputs.index_range().take_back(
r_zone_info.indices.inputs.main.size());
for ([[maybe_unused]] const bNodeLink *link : zone.border_links) {
r_outputs.append_as("Border Link Usage", CPPType::get<bool>());
}
r_zone_info.indices.outputs.border_link_usages = r_outputs.index_range().take_back(
zone.border_links.size());
for (const auto item : body_fn.indices.inputs.attributes_by_field_source_index.items()) {
const int index = r_inputs.append_and_get_index_as(
"Attribute Set", CPPType::get<bke::AnonymousAttributeSet>(), lf::ValueUsage::Maybe);
r_zone_info.indices.inputs.attributes_by_field_source_index.add_new(item.key, index);
}
for (const auto item : body_fn.indices.inputs.attributes_by_caller_propagation_index.items()) {
const int index = r_inputs.append_and_get_index_as(
"Attribute Set", CPPType::get<bke::AnonymousAttributeSet>(), lf::ValueUsage::Maybe);
r_zone_info.indices.inputs.attributes_by_caller_propagation_index.add_new(item.key, index);
}
}
class LazyFunctionForRepeatZone : public LazyFunction {
private:
const bNodeTreeZone &zone_;
@ -1500,53 +1563,10 @@ class LazyFunctionForRepeatZone : public LazyFunction {
body_fn_(body_fn)
{
debug_name_ = "Repeat Zone";
build_interface_for_zone_function(zone, body_fn, zone_info, inputs_, outputs_);
inputs_.append_as("Iterations", CPPType::get<ValueOrField<int>>(), lf::ValueUsage::Used);
for (const bNodeSocket *socket : zone.input_node->input_sockets().drop_front(1).drop_back(1)) {
inputs_.append_as(
socket->name, *socket->typeinfo->geometry_nodes_cpp_type, lf::ValueUsage::Maybe);
}
zone_info.indices.inputs.main = inputs_.index_range();
for (const bNodeLink *link : zone.border_links) {
inputs_.append_as(link->fromsock->name,
*link->tosock->typeinfo->geometry_nodes_cpp_type,
lf::ValueUsage::Maybe);
}
zone_info.indices.inputs.border_links = inputs_.index_range().take_back(
zone.border_links.size());
for (const bNodeSocket *socket : zone.output_node->output_sockets().drop_back(1)) {
inputs_.append_as("Usage", CPPType::get<bool>(), lf::ValueUsage::Maybe);
outputs_.append_as(socket->name, *socket->typeinfo->geometry_nodes_cpp_type);
}
zone_info.indices.inputs.output_usages = inputs_.index_range().take_back(
zone.output_node->output_sockets().drop_back(1).size());
zone_info.indices.outputs.main = outputs_.index_range();
for ([[maybe_unused]] const bNodeSocket *socket :
zone.input_node->input_sockets().drop_back(1)) {
outputs_.append_as("Usage", CPPType::get<bool>());
}
zone_info.indices.outputs.input_usages = outputs_.index_range().take_back(
zone.input_node->input_sockets().drop_back(1).size());
for ([[maybe_unused]] const bNodeLink *link : zone.border_links) {
outputs_.append_as("Border Link Usage", CPPType::get<bool>());
}
zone_info.indices.outputs.border_link_usages = outputs_.index_range().take_back(
zone.border_links.size());
for (const auto item : body_fn_.indices.inputs.attributes_by_field_source_index.items()) {
const int index = inputs_.append_and_get_index_as(
"Attribute Set", CPPType::get<bke::AnonymousAttributeSet>(), lf::ValueUsage::Maybe);
zone_info.indices.inputs.attributes_by_field_source_index.add_new(item.key, index);
}
for (const auto item : body_fn_.indices.inputs.attributes_by_caller_propagation_index.items())
{
const int index = inputs_.append_and_get_index_as(
"Attribute Set", CPPType::get<bke::AnonymousAttributeSet>(), lf::ValueUsage::Maybe);
zone_info.indices.inputs.attributes_by_caller_propagation_index.add_new(item.key, index);
}
/* Iterations input is always used. */
inputs_[zone_info.indices.inputs.main[0]].usage = lf::ValueUsage::Used;
}
void *init_storage(LinearAllocator<> &allocator) const override
@ -1806,6 +1826,241 @@ class LazyFunctionForRepeatZone : public LazyFunction {
}
};
class LazyFunctionForForEachSplit : public lf::LazyFunction {
private:
const int amount_;
public:
LazyFunctionForForEachSplit(const int amount) : amount_(amount)
{
debug_name_ = "For-Each Split";
const CPPType &type = CPPType::get<ValueOrField<int>>();
outputs_.resize(amount, lf::Output("Index", type));
}
void execute_impl(lf::Params &params, const lf::Context & /*context*/) const override
{
for (const int i : IndexRange(amount_)) {
params.set_output(i, ValueOrField<int>(i));
}
}
};
class LazyFunctionForForEachReduce : public lf::LazyFunction {
private:
const int amount_;
public:
LazyFunctionForForEachReduce(const int amount) : amount_(amount)
{
debug_name_ = "For-Each Reduce";
const CPPType &geo_cpp_type = CPPType::get<GeometrySet>();
for ([[maybe_unused]] const int i : IndexRange(amount)) {
inputs_.append_as("Geometry", geo_cpp_type);
}
outputs_.append_as("Geometry", geo_cpp_type);
}
void execute_impl(lf::Params &params, const lf::Context & /*context*/) const override
{
Vector<GeometrySet> geometries(amount_);
for (const int i : IndexRange(amount_)) {
geometries[i] = params.extract_input<GeometrySet>(i);
}
GeometrySet joined_geometries = geometry::join_geometries(geometries, {});
params.set_output(0, std::move(joined_geometries));
}
};
struct ForEachEvalStorage {
LinearAllocator<> allocator;
lf::Graph graph;
std::optional<lf::GraphExecutor> graph_executor;
std::optional<LazyFunctionForForEachSplit> split_fn;
std::optional<LazyFunctionForForEachReduce> reduce_fn;
std::optional<LazyFunctionForLogicalOr> or_fn;
void *graph_executor_storage = nullptr;
Vector<int> input_index_map;
Vector<int> output_index_map;
bool multi_threading_enabled = false;
};
class LazyFunctionForForeachZone : public LazyFunction {
private:
const bNodeTreeZone &zone_;
const ZoneBuildInfo &zone_info_;
const ZoneBodyFunction &body_fn_;
public:
LazyFunctionForForeachZone(const bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn)
: zone_(zone), zone_info_(zone_info), body_fn_(body_fn)
{
debug_name_ = "For-Each Zone";
build_interface_for_zone_function(zone, body_fn, zone_info, inputs_, outputs_);
/* The Amount input is always used. */
inputs_[zone_info.indices.inputs.main[0]].usage = lf::ValueUsage::Used;
}
void *init_storage(LinearAllocator<> &allocator) const override
{
return allocator.construct<ForEachEvalStorage>().release();
}
void destruct_storage(void *storage) const override
{
ForEachEvalStorage *s = static_cast<ForEachEvalStorage *>(storage);
if (s->graph_executor_storage) {
s->graph_executor->destruct_storage(s->graph_executor_storage);
}
std::destroy_at(s);
}
void execute_impl(lf::Params &params, const lf::Context &context) const override
{
auto &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
auto &local_user_data = *static_cast<GeoNodesLFLocalUserData *>(context.local_user_data);
const auto &node_storage = *static_cast<const NodeGeometryForEachOutput *>(
zone_.output_node->storage);
ForEachEvalStorage &eval_storage = *static_cast<ForEachEvalStorage *>(context.storage);
const int iteration_usage_index = zone_info_.indices.outputs.input_usages[0];
if (!params.output_was_set(iteration_usage_index)) {
params.set_output(iteration_usage_index, true);
}
if (!eval_storage.graph_executor) {
this->initialize_execution_graph(
params, eval_storage, node_storage, user_data, local_user_data);
}
lf::RemappedParams eval_graph_params{*eval_storage.graph_executor,
params,
eval_storage.input_index_map,
eval_storage.output_index_map,
eval_storage.multi_threading_enabled};
lf::Context eval_graph_context{
eval_storage.graph_executor_storage, context.user_data, context.local_user_data};
eval_storage.graph_executor->execute(eval_graph_params, eval_graph_context);
}
void initialize_execution_graph(lf::Params &params,
ForEachEvalStorage &eval_storage,
const NodeGeometryForEachOutput &node_storage,
GeoNodesLFUserData &user_data,
GeoNodesLFLocalUserData &local_user_data) const
{
UNUSED_VARS(params, eval_storage, node_storage, user_data, local_user_data);
const int amount = params.get_input<ValueOrField<int>>(0).as_value();
const int main_outputs_num = node_storage.output_items_num;
const int border_links_num = zone_.border_links.size();
lf::Graph &lf_graph = eval_storage.graph;
for (const int i : inputs_.index_range()) {
const lf::Input &input = inputs_[i];
lf_graph.add_input(*input.type, input.debug_name);
}
for (const int i : outputs_.index_range()) {
const lf::Output &output = outputs_[i];
lf_graph.add_output(*output.type, output.debug_name);
}
VectorSet<lf::FunctionNode *> lf_body_nodes;
for ([[maybe_unused]] const int i : IndexRange(amount)) {
lf::FunctionNode &lf_node = lf_graph.add_function(*body_fn_.function);
lf_body_nodes.add_new(&lf_node);
}
eval_storage.split_fn.emplace(amount);
lf::FunctionNode &lf_split_node = lf_graph.add_function(*eval_storage.split_fn);
eval_storage.reduce_fn.emplace(amount);
lf::FunctionNode &lf_reduce_node = lf_graph.add_function(*eval_storage.reduce_fn);
for (const int node_i : IndexRange(amount)) {
lf::FunctionNode &lf_body_node = *lf_body_nodes[node_i];
lf_graph.add_link(lf_split_node.output(node_i),
lf_body_node.input(body_fn_.indices.inputs.main[0]));
lf_graph.add_link(lf_body_node.output(body_fn_.indices.outputs.main[0]),
lf_reduce_node.input(node_i));
for (const int main_output_i : IndexRange(main_outputs_num)) {
lf_graph.add_link(
*lf_graph.graph_inputs()[zone_info_.indices.inputs.output_usages[main_output_i]],
lf_body_node.input(body_fn_.indices.inputs.output_usages[main_output_i]));
}
for (const int border_link_i : IndexRange(border_links_num)) {
lf_graph.add_link(
*lf_graph.graph_inputs()[zone_info_.indices.inputs.border_links[border_link_i]],
lf_body_node.input(body_fn_.indices.inputs.border_links[border_link_i]));
}
}
static bool static_true = true;
for (const int i : zone_info_.indices.outputs.border_link_usages) {
lf_graph.graph_outputs()[i]->set_default_value(&static_true);
}
for (const int i : zone_info_.indices.outputs.input_usages) {
lf_graph.graph_outputs()[i]->set_default_value(&static_true);
}
lf_graph.add_link(lf_reduce_node.output(0), *lf_graph.graph_outputs()[0]);
eval_storage.input_index_map.reinitialize(inputs_.size());
array_utils::fill_index_range<int>(eval_storage.input_index_map);
eval_storage.output_index_map.reinitialize(outputs_.size());
array_utils::fill_index_range<int>(eval_storage.output_index_map);
eval_storage.input_index_map.remove(zone_info_.indices.inputs.main[0]);
eval_storage.output_index_map.remove(zone_info_.indices.outputs.input_usages[0]);
Vector<const lf::GraphInputSocket *> lf_graph_inputs;
Vector<const lf::GraphOutputSocket *> lf_graph_outputs;
for (const int i : eval_storage.input_index_map) {
lf_graph_inputs.append(lf_graph.graph_inputs()[i]);
}
for (const int i : eval_storage.output_index_map) {
lf_graph_outputs.append(lf_graph.graph_outputs()[i]);
}
lf_graph.update_node_indices();
eval_storage.graph_executor.emplace(lf_graph,
std::move(lf_graph_inputs),
std::move(lf_graph_outputs),
nullptr,
nullptr,
nullptr);
eval_storage.graph_executor_storage = eval_storage.graph_executor->init_storage(
eval_storage.allocator);
std::cout << "\n\n" << lf_graph.to_dot() << "\n\n";
}
std::string input_name(const int i) const override
{
if (zone_info_.indices.inputs.output_usages.contains(i)) {
const bNodeSocket &bsocket = zone_.output_node->output_socket(
i - zone_info_.indices.inputs.output_usages.first());
return "Usage: " + StringRef(bsocket.name);
}
return inputs_[i].debug_name;
}
std::string output_name(const int i) const override
{
if (zone_info_.indices.outputs.input_usages.contains(i)) {
const bNodeSocket &bsocket = zone_.input_node->input_socket(
i - zone_info_.indices.outputs.input_usages.first());
return "Usage: " + StringRef(bsocket.name);
}
return outputs_[i].debug_name;
}
};
/**
* Logs intermediate values from the lazy-function graph evaluation into #GeoModifierLog based on
* the mapping between the lazy-function graph and the corresponding #bNodeTree.
@ -2063,6 +2318,10 @@ struct GeometryNodesLazyFunctionBuilder {
this->build_repeat_zone_function(zone);
break;
}
case GEO_NODE_FOR_EACH_OUTPUT: {
this->build_foreach_zone_function(zone);
break;
}
default: {
BLI_assert_unreachable();
break;
@ -2257,6 +2516,14 @@ struct GeometryNodesLazyFunctionBuilder {
zone_info.lazy_function = &zone_fn;
}
void build_foreach_zone_function(const bNodeTreeZone &zone)
{
ZoneBuildInfo &zone_info = zone_build_infos_[zone.index];
ZoneBodyFunction &body_fn = this->build_zone_body_function(zone);
auto &zone_fn = scope_.construct<LazyFunctionForForeachZone>(zone, zone_info, body_fn);
zone_info.lazy_function = &zone_fn;
}
/**
* Build a lazy-function for the "body" of a zone, i.e. for all the nodes within the zone.
*/
@ -2273,7 +2540,11 @@ struct GeometryNodesLazyFunctionBuilder {
Vector<lf::GraphInputSocket *> lf_main_inputs;
Vector<lf::GraphOutputSocket *> lf_main_input_usages;
for (const bNodeSocket *bsocket : zone.input_node->output_sockets().drop_back(1)) {
for (const bNodeSocket *bsocket : zone.input_node->output_sockets()) {
if (bsocket->typeinfo->geometry_nodes_cpp_type == nullptr) {
/* Skip empty sockets. */
continue;
}
lf::GraphInputSocket &lf_input = lf_body_graph.add_input(
*bsocket->typeinfo->geometry_nodes_cpp_type, bsocket->name);
lf::GraphOutputSocket &lf_input_usage = lf_body_graph.add_output(
@ -2293,7 +2564,11 @@ struct GeometryNodesLazyFunctionBuilder {
Vector<lf::GraphOutputSocket *> lf_main_outputs;
Vector<lf::GraphInputSocket *> lf_main_output_usages;
for (const bNodeSocket *bsocket : zone.output_node->input_sockets().drop_back(1)) {
for (const bNodeSocket *bsocket : zone.output_node->input_sockets()) {
if (bsocket->typeinfo->geometry_nodes_cpp_type == nullptr) {
/* Skip empty sockets. */
continue;
}
lf::GraphOutputSocket &lf_output = lf_body_graph.add_output(
*bsocket->typeinfo->geometry_nodes_cpp_type, bsocket->name);
lf::GraphInputSocket &lf_output_usage = lf_body_graph.add_input(
@ -2326,10 +2601,14 @@ struct GeometryNodesLazyFunctionBuilder {
this->insert_nodes_and_zones(zone.child_nodes, zone.child_zones, graph_params);
this->build_output_socket_usages(*zone.input_node, graph_params);
for (const int i : zone.input_node->output_sockets().drop_back(1).index_range()) {
const bNodeSocket &bsocket = zone.input_node->output_socket(i);
lf::OutputSocket *lf_usage = graph_params.usage_by_bsocket.lookup_default(&bsocket, nullptr);
lf::GraphOutputSocket &lf_usage_output = *lf_main_input_usages[i];
int lf_input_index = 0;
for (const bNodeSocket *bsocket : zone.input_node->output_sockets()) {
if (bsocket->typeinfo->geometry_nodes_cpp_type == nullptr) {
/* Skip empty socket. */
continue;
}
lf::OutputSocket *lf_usage = graph_params.usage_by_bsocket.lookup_default(bsocket, nullptr);
lf::GraphOutputSocket &lf_usage_output = *lf_main_input_usages[lf_input_index];
if (lf_usage) {
lf_body_graph.add_link(*lf_usage, lf_usage_output);
}
@ -2337,6 +2616,7 @@ struct GeometryNodesLazyFunctionBuilder {
static const bool static_false = false;
lf_usage_output.set_default_value(&static_false);
}
lf_input_index++;
}
for (const auto item : graph_params.lf_output_by_bsocket.items()) {
@ -2886,6 +3166,7 @@ struct GeometryNodesLazyFunctionBuilder {
*child_zone_info.lazy_function);
mapping_->zone_node_map.add_new(&child_zone, &child_zone_node);
/* TODO: Handle "empty" sockets that are not at the end. */
for (const int i : child_zone_info.indices.inputs.main.index_range()) {
const bNodeSocket &bsocket = child_zone.input_node->input_socket(i);
lf::InputSocket &lf_input_socket = child_zone_node.input(

View File

@ -88,12 +88,32 @@ class RepeatZoneType : public blender::bke::bNodeZoneType {
}
};
class ForEachZoneType : public blender::bke::bNodeZoneType {
public:
ForEachZoneType()
{
this->input_idname = "GeometryNodeForEachInput";
this->output_idname = "GeometryNodeForEachOutput";
this->input_type = GEO_NODE_FOR_EACH_INPUT;
this->output_type = GEO_NODE_FOR_EACH_OUTPUT;
this->theme_id = TH_NODE_ZONE_FOR_EACH;
}
const int &get_corresponding_output_id(const bNode &input_bnode) const override
{
BLI_assert(input_bnode.type == this->input_type);
return static_cast<NodeGeometryForEachInput *>(input_bnode.storage)->output_node_id;
}
};
static void register_zone_types()
{
static SimulationZoneType simulation_zone_type;
static RepeatZoneType repeat_zone_type;
static ForEachZoneType foreach_zone_type;
blender::bke::register_node_zone_type(simulation_zone_type);
blender::bke::register_node_zone_type(repeat_zone_type);
blender::bke::register_node_zone_type(foreach_zone_type);
}
void register_nodes()

View File

@ -56,4 +56,48 @@ void RepeatItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node)
}
}
StructRNA *ForEachInputItemsAccessor::item_srna = &RNA_ForEachInputItem;
int ForEachInputItemsAccessor::node_type = GEO_NODE_FOR_EACH_OUTPUT;
void ForEachInputItemsAccessor::blend_write(BlendWriter *writer, const bNode &node)
{
const auto &storage = *static_cast<const NodeGeometryForEachOutput *>(node.storage);
BLO_write_struct_array(
writer, NodeForEachInputItem, storage.input_items_num, storage.input_items);
for (const NodeForEachInputItem &item : Span(storage.input_items, storage.input_items_num)) {
BLO_write_string(writer, item.name);
}
}
void ForEachInputItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node)
{
auto &storage = *static_cast<NodeGeometryForEachOutput *>(node.storage);
BLO_read_data_address(reader, &storage.input_items);
for (const NodeForEachInputItem &item : Span(storage.input_items, storage.input_items_num)) {
BLO_read_data_address(reader, &item.name);
}
}
StructRNA *ForEachOutputItemsAccessor::item_srna = &RNA_ForEachOutputItem;
int ForEachOutputItemsAccessor::node_type = GEO_NODE_REPEAT_OUTPUT;
void ForEachOutputItemsAccessor::blend_write(BlendWriter *writer, const bNode &node)
{
const auto &storage = *static_cast<const NodeGeometryForEachOutput *>(node.storage);
BLO_write_struct_array(
writer, NodeForEachOutputItem, storage.output_items_num, storage.output_items);
for (const NodeForEachOutputItem &item : Span(storage.output_items, storage.output_items_num)) {
BLO_write_string(writer, item.name);
}
}
void ForEachOutputItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node)
{
auto &storage = *static_cast<NodeGeometryForEachOutput *>(node.storage);
BLO_read_data_address(reader, &storage.output_items);
for (const NodeForEachOutputItem &item : Span(storage.output_items, storage.output_items_num)) {
BLO_read_data_address(reader, &item.name);
}
}
} // namespace blender::nodes