Geometry Nodes: new Repeat Zone

This adds support for running a set of nodes repeatedly. The number
of iterations can be controlled dynamically as an input of the repeat
zone. The repeat zone can be added in via the search or from the
Add > Utilities menu.

The main use case is to replace long repetitive node chains with a more
flexible alternative. Technically, repeat zones can also be used for
many other use cases. However, due to their serial nature, performance
is very  sub-optimal when they are used to solve problems that could
be processed in parallel. Better solutions for such use cases will
be worked on separately.

Repeat zones are similar to simulation zones. The major difference is
that they have no concept of time and are always evaluated entirely in
the current frame, while in simulations only a single iteration is
evaluated per frame.

Stopping the repetition early using a dynamic condition is not yet
supported. "Break" functionality can be implemented manually using
Switch nodes in the  loop for now. It's likely that this functionality
will be built into the repeat zone in the future.
For now, things are kept more simple.

Remaining Todos after this first version:
* Improve socket inspection and viewer node support. Currently, only
  the first iteration is taken into account for socket inspection
  and the viewer.
* Make loop evaluation more lazy. Currently, the evaluation is eager,
  meaning that it evaluates some nodes even though their output may not
  be required.

Pull Request: https://projects.blender.org/blender/blender/pulls/109164
This commit is contained in:
Jacques Lucke 2023-07-11 22:36:10 +02:00
parent 9547e7a317
commit 3d73b71a97
47 changed files with 2070 additions and 105 deletions

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

@ -350,6 +350,105 @@ class SimulationZoneItemMoveOperator(SimulationZoneOperator, Operator):
return {'FINISHED'}
class RepeatZoneOperator:
input_node_type = 'GeometryNodeRepeatInput'
output_node_type = 'GeometryNodeRepeatOutput'
@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):
space = context.space_data
# Needs active node editor and a tree.
if not space or space.type != 'NODE_EDITOR' or not space.edit_tree or space.edit_tree.library:
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
class RepeatZoneItemAddOperator(RepeatZoneOperator, Operator):
"""Add a repeat item to the repeat zone"""
bl_idname = "node.repeat_zone_item_add"
bl_label = "Add Repeat Item"
bl_options = {'REGISTER', 'UNDO'}
default_socket_type = 'GEOMETRY'
def execute(self, context):
node = self.get_output_node(context)
repeat_items = node.repeat_items
# Remember index to move the item.
if node.active_item:
dst_index = node.active_index + 1
dst_type = node.active_item.socket_type
dst_name = node.active_item.name
else:
dst_index = len(repeat_items)
dst_type = self.default_socket_type
# Empty name so it is based on the type.
dst_name = ""
repeat_items.new(dst_type, dst_name)
repeat_items.move(len(repeat_items) - 1, dst_index)
node.active_index = dst_index
return {'FINISHED'}
class RepeatZoneItemRemoveOperator(RepeatZoneOperator, Operator):
"""Remove a repeat item from the repeat zone"""
bl_idname = "node.repeat_zone_item_remove"
bl_label = "Remove Repeat Item"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
node = self.get_output_node(context)
repeat_items = node.repeat_items
if node.active_item:
repeat_items.remove(node.active_item)
node.active_index = min(node.active_index, len(repeat_items) - 1)
return {'FINISHED'}
class RepeatZoneItemMoveOperator(RepeatZoneOperator, Operator):
"""Move a repeat item up or down in the list"""
bl_idname = "node.repeat_zone_item_move"
bl_label = "Move Repeat Item"
bl_options = {'REGISTER', 'UNDO'}
direction: EnumProperty(
name="Direction",
items=[('UP', "Up", ""), ('DOWN', "Down", "")],
default='UP',
)
def execute(self, context):
node = self.get_output_node(context)
repeat_items = node.repeat_items
if self.direction == 'UP' and node.active_index > 0:
repeat_items.move(node.active_index, node.active_index - 1)
node.active_index = node.active_index - 1
elif self.direction == 'DOWN' and node.active_index < len(repeat_items) - 1:
repeat_items.move(node.active_index, node.active_index + 1)
node.active_index = node.active_index + 1
return {'FINISHED'}
classes = (
NewGeometryNodesModifier,
NewGeometryNodeTreeAssign,
@ -357,4 +456,7 @@ classes = (
SimulationZoneItemAddOperator,
SimulationZoneItemRemoveOperator,
SimulationZoneItemMoveOperator,
RepeatZoneItemAddOperator,
RepeatZoneItemRemoveOperator,
RepeatZoneItemMoveOperator,
)

@ -154,14 +154,7 @@ class NODE_OT_add_node(NodeAddOperator, Operator):
return ""
class NODE_OT_add_simulation_zone(NodeAddOperator, Operator):
"""Add simulation zone input and output nodes to the active tree"""
bl_idname = "node.add_simulation_zone"
bl_label = "Add Simulation Zone"
bl_options = {'REGISTER', 'UNDO'}
input_node_type = "GeometryNodeSimulationInput"
output_node_type = "GeometryNodeSimulationOutput"
class NodeAddZoneOperator(NodeAddOperator):
offset: FloatVectorProperty(
name="Offset",
description="Offset of nodes from the cursor when added",
@ -195,6 +188,26 @@ class NODE_OT_add_simulation_zone(NodeAddOperator, Operator):
return {'FINISHED'}
class NODE_OT_add_simulation_zone(NodeAddZoneOperator, Operator):
"""Add simulation zone input and output nodes to the active tree"""
bl_idname = "node.add_simulation_zone"
bl_label = "Add Simulation Zone"
bl_options = {'REGISTER', 'UNDO'}
input_node_type = "GeometryNodeSimulationInput"
output_node_type = "GeometryNodeSimulationOutput"
class NODE_OT_add_repeat_zone(NodeAddZoneOperator, Operator):
"""Add a repeat zone that allows executing nodes a dynamic number of times"""
bl_idname = "node.add_repeat_zone"
bl_label = "Add Repeat Zone"
bl_options = {'REGISTER', 'UNDO'}
input_node_type = "GeometryNodeRepeatInput"
output_node_type = "GeometryNodeRepeatOutput"
class NODE_OT_collapse_hide_unused_toggle(Operator):
"""Toggle collapsed nodes and hide unused sockets"""
@ -328,6 +341,7 @@ classes = (
NODE_OT_add_node,
NODE_OT_add_simulation_zone,
NODE_OT_add_repeat_zone,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_panel_add,
NODE_OT_panel_remove,

@ -72,6 +72,12 @@ def add_simulation_zone(layout, label):
return props
def add_repeat_zone(layout, label):
props = layout.operator("node.add_repeat_zone", text=label)
props.use_transform = True
return props
classes = (
)

@ -530,6 +530,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_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)

@ -1107,6 +1107,80 @@ class NODE_PT_simulation_zone_items(Panel):
layout.prop(active_item, "attribute_domain")
class NODE_UL_repeat_zone_items(bpy.types.UIList):
def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index):
if self.layout_type in {'DEFAULT', 'COMPACT'}:
row = layout.row(align=True)
row.template_node_socket(color=item.color)
row.prop(item, "name", text="", emboss=False, icon_value=icon)
elif self.layout_type == 'GRID':
layout.alignment = 'CENTER'
layout.template_node_socket(color=item.color)
class NODE_PT_repeat_zone_items(Panel):
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = "Node"
bl_label = "Repeat"
input_node_type = 'GeometryNodeRepeatInput'
output_node_type = 'GeometryNodeRepeatOutput'
@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)
split = layout.row()
split.template_list(
"NODE_UL_repeat_zone_items",
"",
output_node,
"repeat_items",
output_node,
"active_index")
ops_col = split.column()
add_remove_col = ops_col.column(align=True)
add_remove_col.operator("node.repeat_zone_item_add", icon='ADD', text="")
add_remove_col.operator("node.repeat_zone_item_remove", icon='REMOVE', text="")
ops_col.separator()
up_down_col = ops_col.column(align=True)
props = up_down_col.operator("node.repeat_zone_item_move", icon='TRIA_UP', text="")
props.direction = 'UP'
props = up_down_col.operator("node.repeat_zone_item_move", icon='TRIA_DOWN', text="")
props.direction = 'DOWN'
active_item = output_node.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'
@ -1174,6 +1248,8 @@ classes = (
NODE_PT_panels,
NODE_UL_simulation_zone_items,
NODE_PT_simulation_zone_items,
NODE_UL_repeat_zone_items,
NODE_PT_repeat_zone_items,
NODE_PT_active_node_properties,
node_panel(EEVEE_MATERIAL_PT_settings),

@ -91,6 +91,8 @@ class SPREADSHEET_HT_header(bpy.types.Header):
layout.label(text=ctx.ui_name, icon='NODE')
elif ctx.type == 'SIMULATION_ZONE':
layout.label(text="Simulation Zone")
elif ctx.type == 'REPEAT_ZONE':
layout.label(text="Repeat Zone")
elif ctx.type == 'VIEWER_NODE':
layout.label(text=ctx.ui_name)

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

@ -1361,6 +1361,10 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_INPUT_SIGNED_DISTANCE 2102
#define GEO_NODE_SAMPLE_VOLUME 2103
#define GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_EDGE 2104
/* Leaving out two indices to avoid crashes with files that were created during the development of
* the repeat zone. */
#define GEO_NODE_REPEAT_INPUT 2107
#define GEO_NODE_REPEAT_OUTPUT 2108
/** \} */

@ -53,6 +53,7 @@ ModifierViewerPathElem *BKE_viewer_path_elem_new_modifier(void);
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);
ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src);
bool BKE_viewer_path_elem_equal(const ViewerPathElem *a, const ViewerPathElem *b);
void BKE_viewer_path_elem_free(ViewerPathElem *elem);

@ -88,4 +88,33 @@ void SimulationZoneComputeContext::print_current_in_line(std::ostream &stream) c
stream << "Simulation Zone ID: " << output_node_id_;
}
RepeatZoneComputeContext::RepeatZoneComputeContext(const ComputeContext *parent,
const int32_t output_node_id,
const int iteration)
: ComputeContext(s_static_type, parent), output_node_id_(output_node_id), iteration_(iteration)
{
/* 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), &iteration_, sizeof(int));
hash_.mix_in(buffer, buffer_size);
}
RepeatZoneComputeContext::RepeatZoneComputeContext(const ComputeContext *parent,
const bNode &node,
const int iteration)
: RepeatZoneComputeContext(parent, node.identifier, iteration)
{
}
void RepeatZoneComputeContext::print_current_in_line(std::ostream &stream) const
{
stream << "Repeat Zone ID: " << output_node_id_;
}
} // namespace blender::bke

@ -675,6 +675,14 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
BLO_write_string(writer, item.name);
}
}
if (node->type == GEO_NODE_REPEAT_OUTPUT) {
const NodeGeometryRepeatOutput &storage = *static_cast<const NodeGeometryRepeatOutput *>(
node->storage);
BLO_write_struct_array(writer, NodeRepeatItem, storage.items_num, storage.items);
for (const NodeRepeatItem &item : Span(storage.items, storage.items_num)) {
BLO_write_string(writer, item.name);
}
}
}
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
@ -871,6 +879,15 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree)
}
break;
}
case GEO_NODE_REPEAT_OUTPUT: {
NodeGeometryRepeatOutput &storage = *static_cast<NodeGeometryRepeatOutput *>(
node->storage);
BLO_read_data_address(reader, &storage.items);
for (const NodeRepeatItem &item : Span(storage.items, storage.items_num)) {
BLO_read_data_address(reader, &item.name);
}
break;
}
default:
break;

@ -294,6 +294,17 @@ static Vector<const bNode *> get_implicit_origin_nodes(const bNodeTree &ntree, b
}
}
}
if (node.type == GEO_NODE_REPEAT_OUTPUT) {
for (const bNode *repeat_input_node :
ntree.runtime->nodes_by_type.lookup(nodeTypeFind("GeometryNodeRepeatInput")))
{
const auto &storage = *static_cast<const NodeGeometryRepeatInput *>(
repeat_input_node->storage);
if (storage.output_node_id == node.identifier) {
origin_nodes.append(repeat_input_node);
}
}
}
return origin_nodes;
}
@ -306,6 +317,12 @@ static Vector<const bNode *> get_implicit_target_nodes(const bNodeTree &ntree, b
target_nodes.append(sim_output_node);
}
}
if (node.type == GEO_NODE_REPEAT_INPUT) {
const auto &storage = *static_cast<const NodeGeometryRepeatInput *>(node.storage);
if (const bNode *repeat_output_node = ntree.node_by_id(storage.output_node_id)) {
target_nodes.append(repeat_output_node);
}
}
return target_nodes;
}

@ -99,6 +99,48 @@ static const aal::RelationsInNode &get_relations_in_node(const bNode &node, Reso
}
return relations;
}
if (ELEM(node.type, GEO_NODE_REPEAT_INPUT, GEO_NODE_REPEAT_OUTPUT)) {
aal::RelationsInNode &relations = scope.construct<aal::RelationsInNode>();
/* TODO: Add a smaller set of relations. This requires changing the inferencing algorithm to
* make it aware of loops. */
for (const bNodeSocket *socket : node.output_sockets()) {
if (socket->type == SOCK_GEOMETRY) {
for (const bNodeSocket *other_output : node.output_sockets()) {
if (socket_is_field(*other_output)) {
relations.available_relations.append({other_output->index(), socket->index()});
}
}
for (const bNodeSocket *input_socket : node.input_sockets()) {
if (input_socket->type == SOCK_GEOMETRY) {
relations.propagate_relations.append({input_socket->index(), socket->index()});
}
}
}
else if (socket_is_field(*socket)) {
/* Reference relations are not added for the output node, because then nodes after the
* repeat zone would have to know about the individual field sources within the repeat
* zone. This is not necessary, because the field outputs of a repeat zone already serve as
* field sources and anonymous attributes are extracted from them. */
if (node.type == GEO_NODE_REPEAT_INPUT) {
for (const bNodeSocket *input_socket : node.input_sockets()) {
if (socket_is_field(*input_socket)) {
relations.reference_relations.append({input_socket->index(), socket->index()});
}
}
}
}
}
for (const bNodeSocket *socket : node.input_sockets()) {
if (socket->type == SOCK_GEOMETRY) {
for (const bNodeSocket *other_input : node.input_sockets()) {
if (socket_is_field(*other_input)) {
relations.eval_relations.append({other_input->index(), socket->index()});
}
}
}
}
return relations;
}
if (const NodeDeclaration *node_decl = node.declaration()) {
if (const aal::RelationsInNode *relations = node_decl->anonymous_attribute_relations()) {
return *relations;

@ -319,6 +319,22 @@ static eFieldStateSyncResult simulation_nodes_field_state_sync(
return res;
}
static eFieldStateSyncResult repeat_field_state_sync(
const bNode &input_node,
const bNode &output_node,
const MutableSpan<SocketFieldState> field_state_by_socket_id)
{
eFieldStateSyncResult res = eFieldStateSyncResult::NONE;
for (const int i : output_node.output_sockets().index_range()) {
const bNodeSocket &input_socket = input_node.output_socket(i);
const bNodeSocket &output_socket = output_node.output_socket(i);
SocketFieldState &input_state = field_state_by_socket_id[input_socket.index_in_tree()];
SocketFieldState &output_state = field_state_by_socket_id[output_socket.index_in_tree()];
res |= sync_field_states(input_state, output_state);
}
return res;
}
static bool propagate_special_data_requirements(
const bNodeTree &tree,
const bNode &node,
@ -328,29 +344,59 @@ static bool propagate_special_data_requirements(
bool need_update = false;
/* Sync field state between simulation nodes and schedule another pass if necessary. */
if (node.type == GEO_NODE_SIMULATION_INPUT) {
const NodeGeometrySimulationInput &data = *static_cast<const NodeGeometrySimulationInput *>(
node.storage);
if (const bNode *output_node = tree.node_by_id(data.output_node_id)) {
const eFieldStateSyncResult sync_result = simulation_nodes_field_state_sync(
node, *output_node, field_state_by_socket_id);
if (bool(sync_result & eFieldStateSyncResult::CHANGED_B)) {
need_update = true;
}
}
}
else if (node.type == GEO_NODE_SIMULATION_OUTPUT) {
for (const bNode *input_node : tree.nodes_by_type("GeometryNodeSimulationInput")) {
/* Sync field state between zone nodes and schedule another pass if necessary. */
switch (node.type) {
case GEO_NODE_SIMULATION_INPUT: {
const NodeGeometrySimulationInput &data = *static_cast<const NodeGeometrySimulationInput *>(
input_node->storage);
if (node.identifier == data.output_node_id) {
node.storage);
if (const bNode *output_node = tree.node_by_id(data.output_node_id)) {
const eFieldStateSyncResult sync_result = simulation_nodes_field_state_sync(
*input_node, node, field_state_by_socket_id);
if (bool(sync_result & eFieldStateSyncResult::CHANGED_A)) {
node, *output_node, field_state_by_socket_id);
if (bool(sync_result & eFieldStateSyncResult::CHANGED_B)) {
need_update = true;
}
}
break;
}
case GEO_NODE_SIMULATION_OUTPUT: {
for (const bNode *input_node : tree.nodes_by_type("GeometryNodeSimulationInput")) {
const NodeGeometrySimulationInput &data =
*static_cast<const NodeGeometrySimulationInput *>(input_node->storage);
if (node.identifier == data.output_node_id) {
const eFieldStateSyncResult sync_result = simulation_nodes_field_state_sync(
*input_node, node, field_state_by_socket_id);
if (bool(sync_result & eFieldStateSyncResult::CHANGED_A)) {
need_update = true;
}
}
}
break;
}
case GEO_NODE_REPEAT_INPUT: {
const NodeGeometryRepeatInput &data = *static_cast<const NodeGeometryRepeatInput *>(
node.storage);
if (const bNode *output_node = tree.node_by_id(data.output_node_id)) {
const eFieldStateSyncResult sync_result = repeat_field_state_sync(
node, *output_node, field_state_by_socket_id);
if (bool(sync_result & eFieldStateSyncResult::CHANGED_B)) {
need_update = true;
}
}
break;
}
case GEO_NODE_REPEAT_OUTPUT: {
for (const bNode *input_node : tree.nodes_by_type("GeometryNodeRepeatInput")) {
const NodeGeometryRepeatInput &data = *static_cast<const NodeGeometryRepeatInput *>(
input_node->storage);
if (node.identifier == data.output_node_id) {
const eFieldStateSyncResult sync_result = repeat_field_state_sync(
*input_node, node, field_state_by_socket_id);
if (bool(sync_result & eFieldStateSyncResult::CHANGED_A)) {
need_update = true;
}
}
}
break;
}
}
@ -365,8 +411,8 @@ static void propagate_data_requirements_from_right_to_left(
const Span<const bNode *> toposort_result = tree.toposort_right_to_left();
while (true) {
/* Node updates may require several passes due to cyclic dependencies caused by simulation
* input/output nodes. */
/* Node updates may require several passes due to cyclic dependencies caused by simulation or
* repeat input/output nodes. */
bool need_update = false;
for (const bNode *node : toposort_result) {

@ -588,6 +588,15 @@ class NodeTreeMainUpdater {
}
}
}
if (node.type == GEO_NODE_REPEAT_INPUT) {
const NodeGeometryRepeatInput *data = static_cast<const NodeGeometryRepeatInput *>(
node.storage);
if (const bNode *output_node = ntree.node_by_id(data->output_node_id)) {
if (output_node->runtime->changed_flag & NTREE_CHANGED_NODE_PROPERTY) {
return true;
}
}
}
return false;
}

@ -33,7 +33,10 @@ static Vector<std::unique_ptr<bNodeTreeZone>> find_zone_nodes(
Map<const bNode *, bNodeTreeZone *> &r_zone_by_inout_node)
{
Vector<std::unique_ptr<bNodeTreeZone>> zones;
for (const bNode *node : tree.nodes_by_type("GeometryNodeSimulationOutput")) {
Vector<const bNode *> zone_output_nodes;
zone_output_nodes.extend(tree.nodes_by_type("GeometryNodeSimulationOutput"));
zone_output_nodes.extend(tree.nodes_by_type("GeometryNodeRepeatOutput"));
for (const bNode *node : zone_output_nodes) {
auto zone = std::make_unique<bNodeTreeZone>();
zone->owner = &owner;
zone->index = zones.size();
@ -50,6 +53,15 @@ static Vector<std::unique_ptr<bNodeTreeZone>> find_zone_nodes(
}
}
}
for (const bNode *node : tree.nodes_by_type("GeometryNodeRepeatInput")) {
const auto &storage = *static_cast<NodeGeometryRepeatInput *>(node->storage);
if (const bNode *repeat_output_node = tree.node_by_id(storage.output_node_id)) {
if (bNodeTreeZone *zone = r_zone_by_inout_node.lookup_default(repeat_output_node, nullptr)) {
zone->input_node = node;
r_zone_by_inout_node.add(node, zone);
}
}
}
return zones;
}
@ -227,13 +239,13 @@ static std::unique_ptr<bNodeTreeZones> discover_tree_zones(const bNodeTree &tree
depend_on_output_flags |= depend_on_output_flag_array[from_node_i];
}
}
if (node->type == GEO_NODE_SIMULATION_INPUT) {
if (ELEM(node->type, GEO_NODE_SIMULATION_INPUT, GEO_NODE_REPEAT_INPUT)) {
if (const bNodeTreeZone *zone = zone_by_inout_node.lookup_default(node, nullptr)) {
/* Now entering a zone, so set the corresponding bit. */
depend_on_input_flags[zone->index].set();
}
}
else if (node->type == GEO_NODE_SIMULATION_OUTPUT) {
else if (ELEM(node->type, GEO_NODE_SIMULATION_OUTPUT, GEO_NODE_REPEAT_OUTPUT)) {
if (const bNodeTreeZone *zone = zone_by_inout_node.lookup_default(node, nullptr)) {
/* The output is implicitly linked to the input, so also propagate the bits from there. */
if (const bNode *zone_input_node = zone->input_node) {

@ -88,6 +88,11 @@ void BKE_viewer_path_blend_write(BlendWriter *writer, const ViewerPath *viewer_p
BLO_write_struct(writer, ViewerNodeViewerPathElem, typed_elem);
break;
}
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
const auto *typed_elem = reinterpret_cast<RepeatZoneViewerPathElem *>(elem);
BLO_write_struct(writer, RepeatZoneViewerPathElem, typed_elem);
break;
}
}
BLO_write_string(writer, elem->ui_name);
}
@ -102,6 +107,7 @@ void BKE_viewer_path_blend_read_data(BlendDataReader *reader, ViewerPath *viewer
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_ID: {
break;
}
@ -126,7 +132,8 @@ void BKE_viewer_path_blend_read_lib(BlendLibReader *reader, ID *self_id, ViewerP
case VIEWER_PATH_ELEM_TYPE_MODIFIER:
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_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
break;
}
}
@ -145,7 +152,8 @@ void BKE_viewer_path_foreach_id(LibraryForeachIDData *data, ViewerPath *viewer_p
case VIEWER_PATH_ELEM_TYPE_MODIFIER:
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_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
break;
}
}
@ -164,7 +172,8 @@ void BKE_viewer_path_id_remap(ViewerPath *viewer_path, const IDRemapper *mapping
case VIEWER_PATH_ELEM_TYPE_MODIFIER:
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_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
break;
}
}
@ -196,6 +205,9 @@ ViewerPathElem *BKE_viewer_path_elem_new(const ViewerPathElemType type)
case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: {
return &make_elem<ViewerNodeViewerPathElem>(type)->base;
}
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
return &make_elem<RepeatZoneViewerPathElem>(type)->base;
}
}
BLI_assert_unreachable();
return nullptr;
@ -230,6 +242,12 @@ ViewerNodeViewerPathElem *BKE_viewer_path_elem_new_viewer_node()
BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_VIEWER_NODE));
}
RepeatZoneViewerPathElem *BKE_viewer_path_elem_new_repeat_zone()
{
return reinterpret_cast<RepeatZoneViewerPathElem *>(
BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE));
}
ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src)
{
ViewerPathElem *dst = BKE_viewer_path_elem_new(ViewerPathElemType(src->type));
@ -269,6 +287,13 @@ ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src)
new_elem->node_id = old_elem->node_id;
break;
}
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
const auto *old_elem = reinterpret_cast<const RepeatZoneViewerPathElem *>(src);
auto *new_elem = reinterpret_cast<RepeatZoneViewerPathElem *>(dst);
new_elem->repeat_output_node_id = old_elem->repeat_output_node_id;
new_elem->iteration = old_elem->iteration;
break;
}
}
return dst;
}
@ -304,6 +329,12 @@ bool BKE_viewer_path_elem_equal(const ViewerPathElem *a, const ViewerPathElem *b
const auto *b_elem = reinterpret_cast<const ViewerNodeViewerPathElem *>(b);
return a_elem->node_id == b_elem->node_id;
}
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
const auto *a_elem = reinterpret_cast<const RepeatZoneViewerPathElem *>(a);
const auto *b_elem = reinterpret_cast<const RepeatZoneViewerPathElem *>(b);
return a_elem->repeat_output_node_id == b_elem->repeat_output_node_id &&
a_elem->iteration == b_elem->iteration;
}
}
return false;
}
@ -314,7 +345,8 @@ void BKE_viewer_path_elem_free(ViewerPathElem *elem)
case VIEWER_PATH_ELEM_TYPE_ID:
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_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
break;
}
case VIEWER_PATH_ELEM_TYPE_MODIFIER: {

@ -116,6 +116,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_repeat);
}
#undef FROM_DEFAULT_V4_UCHAR

@ -181,6 +181,7 @@ typedef enum ThemeColorID {
TH_NODE_ATTRIBUTE,
TH_NODE_ZONE_SIMULATION,
TH_NODE_ZONE_REPEAT,
TH_SIMULATED_FRAMES,
TH_CONSOLE_OUTPUT,

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

@ -23,6 +23,7 @@
#include "DNA_world_types.h"
#include "BLI_array.hh"
#include "BLI_bounds.hh"
#include "BLI_convexhull_2d.h"
#include "BLI_map.hh"
#include "BLI_set.hh"
@ -2195,7 +2196,12 @@ static void node_draw_basis(const bContext &C,
}
/* Shadow. */
if (!ELEM(node.type, GEO_NODE_SIMULATION_INPUT, GEO_NODE_SIMULATION_OUTPUT)) {
if (!ELEM(node.type,
GEO_NODE_SIMULATION_INPUT,
GEO_NODE_SIMULATION_OUTPUT,
GEO_NODE_REPEAT_INPUT,
GEO_NODE_REPEAT_OUTPUT))
{
node_draw_shadow(snode, node, BASIS_RAD, 1.0f);
}
@ -2475,6 +2481,10 @@ static void node_draw_basis(const bContext &C,
UI_GetThemeColor4fv(TH_NODE_ZONE_SIMULATION, color_outline);
color_outline[3] = 1.0f;
}
else if (ELEM(node.type, GEO_NODE_REPEAT_INPUT, GEO_NODE_REPEAT_OUTPUT)) {
UI_GetThemeColor4fv(TH_NODE_ZONE_REPEAT, color_outline);
color_outline[3] = 1.0f;
}
else {
UI_GetThemeColorBlendShade4fv(TH_BACK, TH_NODE, 0.4f, -20, color_outline);
}
@ -3177,6 +3187,8 @@ static void node_draw_zones(TreeDrawContext & /*tree_draw_ctx*/,
Array<Vector<float2>> bounds_by_zone(zones->zones.size());
Array<bke::CurvesGeometry> fillet_curve_by_zone(zones->zones.size());
/* Bounding box area of zones is used to determine draw order. */
Array<float> bounding_box_area_by_zone(zones->zones.size());
for (const int zone_i : zones->zones.index_range()) {
const bNodeTreeZone &zone = *zones->zones[zone_i];
@ -3185,6 +3197,11 @@ static void node_draw_zones(TreeDrawContext & /*tree_draw_ctx*/,
const Span<float2> boundary_positions = bounds_by_zone[zone_i];
const int boundary_positions_num = boundary_positions.size();
const Bounds<float2> bounding_box = *bounds::min_max(boundary_positions);
const float bounding_box_area = (bounding_box.max.x - bounding_box.min.x) *
(bounding_box.max.y - bounding_box.min.y);
bounding_box_area_by_zone[zone_i] = bounding_box_area;
bke::CurvesGeometry boundary_curve(boundary_positions_num, 1);
boundary_curve.cyclic_for_write().first() = true;
boundary_curve.fill_curve_types(CURVE_TYPE_POLY);
@ -3209,21 +3226,38 @@ static void node_draw_zones(TreeDrawContext & /*tree_draw_ctx*/,
float line_width = 1.0f * scale;
float viewport[4] = {};
GPU_viewport_size_get_f(viewport);
float zone_color[4];
UI_GetThemeColor4fv(TH_NODE_ZONE_SIMULATION, zone_color);
const auto get_theme_id = [&](const int zone_i) {
const bNode *node = zones->zones[zone_i]->output_node;
if (node->type == GEO_NODE_SIMULATION_OUTPUT) {
return TH_NODE_ZONE_SIMULATION;
}
return TH_NODE_ZONE_REPEAT;
};
const uint pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
/* Draw all the contour lines after to prevent them from getting hidden by overlapping zones. */
Vector<int> zone_draw_order;
for (const int zone_i : zones->zones.index_range()) {
zone_draw_order.append(zone_i);
}
std::sort(zone_draw_order.begin(), zone_draw_order.end(), [&](const int a, const int b) {
/* Draw zones with smaller bounding box on top to make them visible. */
return bounding_box_area_by_zone[a] > bounding_box_area_by_zone[b];
});
/* Draw all the contour lines after to prevent them from getting hidden by overlapping zones. */
for (const int zone_i : zone_draw_order) {
float zone_color[4];
UI_GetThemeColor4fv(get_theme_id(zone_i), zone_color);
if (zone_color[3] == 0.0f) {
break;
}
const Span<float3> fillet_boundary_positions = fillet_curve_by_zone[zone_i].positions();
/* Draw the background. */
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformThemeColorBlend(TH_BACK, TH_NODE_ZONE_SIMULATION, zone_color[3]);
immUniformThemeColorBlend(TH_BACK, get_theme_id(zone_i), zone_color[3]);
immBegin(GPU_PRIM_TRI_FAN, fillet_boundary_positions.size() + 1);
for (const float3 &p : fillet_boundary_positions) {
@ -3235,7 +3269,7 @@ static void node_draw_zones(TreeDrawContext & /*tree_draw_ctx*/,
immUnbindProgram();
}
for (const int zone_i : zones->zones.index_range()) {
for (const int zone_i : zone_draw_order) {
const Span<float3> fillet_boundary_positions = fillet_curve_by_zone[zone_i].positions();
/* Draw the contour lines. */
immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR);
@ -3243,7 +3277,7 @@ static void node_draw_zones(TreeDrawContext & /*tree_draw_ctx*/,
immUniform2fv("viewportSize", &viewport[2]);
immUniform1f("lineWidth", line_width * U.pixelsize);
immUniformThemeColorAlpha(TH_NODE_ZONE_SIMULATION, 1.0f);
immUniformThemeColorAlpha(get_theme_id(zone_i), 1.0f);
immBegin(GPU_PRIM_LINE_STRIP, fillet_boundary_positions.size() + 1);
for (const float3 &p : fillet_boundary_positions) {
immVertex3fv(pos, p);

@ -1273,22 +1273,37 @@ void remap_node_pairing(bNodeTree &dst_tree, const Map<const bNode *, bNode *> &
* so we have to build a map first to find copied output nodes in the new tree. */
Map<int32_t, bNode *> dst_output_node_map;
for (const auto &item : node_map.items()) {
if (item.key->type == GEO_NODE_SIMULATION_OUTPUT) {
if (ELEM(item.key->type, GEO_NODE_SIMULATION_OUTPUT, GEO_NODE_REPEAT_OUTPUT)) {
dst_output_node_map.add_new(item.key->identifier, item.value);
}
}
for (bNode *dst_node : node_map.values()) {
if (dst_node->type == GEO_NODE_SIMULATION_INPUT) {
NodeGeometrySimulationInput *data = static_cast<NodeGeometrySimulationInput *>(
dst_node->storage);
if (const bNode *output_node = dst_output_node_map.lookup_default(data->output_node_id,
nullptr)) {
data->output_node_id = output_node->identifier;
switch (dst_node->type) {
case GEO_NODE_SIMULATION_INPUT: {
NodeGeometrySimulationInput *data = static_cast<NodeGeometrySimulationInput *>(
dst_node->storage);
if (const bNode *output_node = dst_output_node_map.lookup_default(data->output_node_id,
nullptr)) {
data->output_node_id = output_node->identifier;
}
else {
data->output_node_id = 0;
blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node);
}
break;
}
else {
data->output_node_id = 0;
blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node);
case GEO_NODE_REPEAT_INPUT: {
NodeGeometryRepeatInput *data = static_cast<NodeGeometryRepeatInput *>(dst_node->storage);
if (const bNode *output_node = dst_output_node_map.lookup_default(data->output_node_id,
nullptr)) {
data->output_node_id = output_node->identifier;
}
else {
data->output_node_id = 0;
blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node);
}
break;
}
}
}

@ -147,16 +147,31 @@ static void remap_pairing(bNodeTree &dst_tree,
const Map<int32_t, int32_t> &identifier_map)
{
for (bNode *dst_node : nodes) {
if (dst_node->type == GEO_NODE_SIMULATION_INPUT) {
NodeGeometrySimulationInput *data = static_cast<NodeGeometrySimulationInput *>(
dst_node->storage);
if (data->output_node_id == 0) {
continue;
}
switch (dst_node->type) {
case GEO_NODE_SIMULATION_INPUT: {
NodeGeometrySimulationInput *data = static_cast<NodeGeometrySimulationInput *>(
dst_node->storage);
if (data->output_node_id == 0) {
continue;
}
data->output_node_id = identifier_map.lookup_default(data->output_node_id, 0);
if (data->output_node_id == 0) {
blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node);
data->output_node_id = identifier_map.lookup_default(data->output_node_id, 0);
if (data->output_node_id == 0) {
blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node);
}
break;
}
case GEO_NODE_REPEAT_INPUT: {
NodeGeometryRepeatInput *data = static_cast<NodeGeometryRepeatInput *>(dst_node->storage);
if (data->output_node_id == 0) {
continue;
}
data->output_node_id = identifier_map.lookup_default(data->output_node_id, 0);
if (data->output_node_id == 0) {
blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node);
}
break;
}
}
}
@ -803,6 +818,31 @@ static bool node_group_make_test_selected(bNodeTree &ntree,
}
}
}
for (bNode *input_node : ntree.nodes_by_type("GeometryNodeRepeatInput")) {
const NodeGeometryRepeatInput &input_data = *static_cast<const NodeGeometryRepeatInput *>(
input_node->storage);
if (bNode *output_node = ntree.node_by_id(input_data.output_node_id)) {
const bool input_selected = nodes_to_group.contains(input_node);
const bool output_selected = nodes_to_group.contains(output_node);
if (input_selected && !output_selected) {
BKE_reportf(&reports,
RPT_WARNING,
"Can not add repeat input node '%s' to a group without its paired output '%s'",
input_node->name,
output_node->name);
return false;
}
if (output_selected && !input_selected) {
BKE_reportf(&reports,
RPT_WARNING,
"Can not add repeat output node '%s' to a group without its paired input '%s'",
output_node->name,
input_node->name);
return false;
}
}
}
return true;
}

@ -327,6 +327,17 @@ void node_select_paired(bNodeTree &node_tree)
}
}
}
for (bNode *input_node : node_tree.nodes_by_type("GeometryNodeRepeatInput")) {
const auto *storage = static_cast<const NodeGeometryRepeatInput *>(input_node->storage);
if (bNode *output_node = node_tree.node_by_id(storage->output_node_id)) {
if (input_node->flag & NODE_SELECT) {
output_node->flag |= NODE_SELECT;
}
if (output_node->flag & NODE_SELECT) {
input_node->flag |= NODE_SELECT;
}
}
}
}
VectorSet<bNode *> get_selected_nodes(bNodeTree &node_tree)

@ -26,6 +26,25 @@ namespace blender::ed::viewer_path {
using bke::bNodeTreeZone;
using bke::bNodeTreeZones;
static ViewerPathElem *viewer_path_elem_for_zone(const bNodeTreeZone &zone)
{
switch (zone.output_node->type) {
case GEO_NODE_SIMULATION_OUTPUT: {
SimulationZoneViewerPathElem *node_elem = BKE_viewer_path_elem_new_simulation_zone();
node_elem->sim_output_node_id = zone.output_node->identifier;
return &node_elem->base;
}
case GEO_NODE_REPEAT_OUTPUT: {
RepeatZoneViewerPathElem *node_elem = BKE_viewer_path_elem_new_repeat_zone();
node_elem->repeat_output_node_id = zone.output_node->identifier;
node_elem->iteration = 0;
return &node_elem->base;
}
}
BLI_assert_unreachable();
return nullptr;
}
static void viewer_path_for_geometry_node(const SpaceNode &snode,
const bNode &node,
ViewerPath &r_dst)
@ -83,9 +102,8 @@ static void viewer_path_for_geometry_node(const SpaceNode &snode,
const Vector<const bNodeTreeZone *> zone_stack = tree_zones->get_zone_stack_for_node(
node->identifier);
for (const bNodeTreeZone *zone : zone_stack) {
SimulationZoneViewerPathElem *node_elem = BKE_viewer_path_elem_new_simulation_zone();
node_elem->sim_output_node_id = zone->output_node->identifier;
BLI_addtail(&r_dst.path, node_elem);
ViewerPathElem *zone_elem = viewer_path_elem_for_zone(*zone);
BLI_addtail(&r_dst.path, zone_elem);
}
GroupNodeViewerPathElem *node_elem = BKE_viewer_path_elem_new_group_node();
@ -102,9 +120,8 @@ static void viewer_path_for_geometry_node(const SpaceNode &snode,
const Vector<const bNodeTreeZone *> zone_stack = tree_zones->get_zone_stack_for_node(
node.identifier);
for (const bNodeTreeZone *zone : zone_stack) {
SimulationZoneViewerPathElem *node_elem = BKE_viewer_path_elem_new_simulation_zone();
node_elem->sim_output_node_id = zone->output_node->identifier;
BLI_addtail(&r_dst.path, node_elem);
ViewerPathElem *zone_elem = viewer_path_elem_for_zone(*zone);
BLI_addtail(&r_dst.path, zone_elem);
}
ViewerNodeViewerPathElem *viewer_node_elem = BKE_viewer_path_elem_new_viewer_node();
@ -221,7 +238,10 @@ std::optional<ViewerPathForGeometryNodesViewer> parse_geometry_nodes_viewer(
remaining_elems = remaining_elems.drop_front(1);
Vector<const ViewerPathElem *> node_path;
for (const ViewerPathElem *elem : remaining_elems.drop_back(1)) {
if (!ELEM(elem->type, VIEWER_PATH_ELEM_TYPE_GROUP_NODE, VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE))
if (!ELEM(elem->type,
VIEWER_PATH_ELEM_TYPE_GROUP_NODE,
VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE,
VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE))
{
return std::nullopt;
}
@ -275,6 +295,19 @@ bool exists_geometry_nodes_viewer(const ViewerPathForGeometryNodesViewer &parsed
zone = next_zone;
break;
}
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
const auto &typed_elem = *reinterpret_cast<const RepeatZoneViewerPathElem *>(path_elem);
const bNodeTreeZone *next_zone = tree_zones->get_zone_by_node(
typed_elem.repeat_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);

@ -16,6 +16,7 @@
/** Workaround to forward-declare C++ type in C header. */
#ifdef __cplusplus
# include <BLI_vector.hh>
# include <string>
namespace blender {
template<typename T> class Span;
@ -1745,6 +1746,44 @@ typedef struct NodeGeometrySimulationOutput {
#endif
} NodeGeometrySimulationOutput;
typedef struct NodeRepeatItem {
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;
#ifdef __cplusplus
static bool supports_type(eNodeSocketDatatype type);
std::string identifier_str() const;
#endif
} NodeRepeatItem;
typedef struct NodeGeometryRepeatInput {
/** bNode.identifier of the corresponding output node. */
int32_t output_node_id;
} NodeGeometryRepeatInput;
typedef struct NodeGeometryRepeatOutput {
NodeRepeatItem *items;
int items_num;
int active_index;
/** Identifier to give to the next repeat item. */
int next_identifier;
char _pad[4];
#ifdef __cplusplus
blender::Span<NodeRepeatItem> items_span() const;
blender::MutableSpan<NodeRepeatItem> items_span();
NodeRepeatItem *add_item(const char *name, eNodeSocketDatatype type);
void set_item_name(NodeRepeatItem &item, const char *name);
#endif
} NodeGeometryRepeatOutput;
typedef struct NodeGeometryDistributePointsInVolume {
/** #GeometryNodePointDistributeVolumeMode. */
uint8_t mode;

@ -346,6 +346,8 @@ typedef struct ThemeSpace {
unsigned char nodeclass_geometry[4], nodeclass_attribute[4];
unsigned char node_zone_simulation[4];
unsigned char node_zone_repeat[4];
unsigned char _pad9[4];
unsigned char simulated_frames[4];
/** For sequence editor. */

@ -15,6 +15,7 @@ typedef enum ViewerPathElemType {
VIEWER_PATH_ELEM_TYPE_GROUP_NODE = 2,
VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE = 3,
VIEWER_PATH_ELEM_TYPE_VIEWER_NODE = 4,
VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE = 5,
} ViewerPathElemType;
typedef struct ViewerPathElem {
@ -48,6 +49,13 @@ typedef struct SimulationZoneViewerPathElem {
char _pad1[4];
} SimulationZoneViewerPathElem;
typedef struct RepeatZoneViewerPathElem {
ViewerPathElem base;
int repeat_output_node_id;
int iteration;
} RepeatZoneViewerPathElem;
typedef struct ViewerNodeViewerPathElem {
ViewerPathElem base;

@ -35,6 +35,7 @@
#include "BKE_geometry_set.hh"
#include "BKE_image.h"
#include "BKE_node.h"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.h"
#include "BKE_texture.h"
@ -62,6 +63,8 @@
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "BLI_string_utils.h"
const EnumPropertyItem rna_enum_node_socket_in_out_items[] = {{SOCK_IN, "IN", 0, "Input", ""},
{SOCK_OUT, "OUT", 0, "Output", ""},
{0, nullptr, 0, nullptr, nullptr}};
@ -4261,6 +4264,29 @@ static void rna_SimulationStateItem_update(Main *bmain, Scene * /*scene*/, Point
ED_node_tree_propagate_change(nullptr, bmain, ntree);
}
static bNode *find_node_by_repeat_item(PointerRNA *ptr)
{
const NodeRepeatItem *item = static_cast<const NodeRepeatItem *>(ptr->data);
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
ntree->ensure_topology_cache();
for (bNode *node : ntree->nodes_by_type("GeometryNodeRepeatOutput")) {
auto *storage = static_cast<NodeGeometryRepeatOutput *>(node->storage);
if (storage->items_span().contains_ptr(item)) {
return node;
}
}
return nullptr;
}
static void rna_RepeatItem_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr)
{
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode *node = find_node_by_repeat_item(ptr);
BKE_ntree_update_tag_node_property(ntree, node);
ED_node_tree_propagate_change(nullptr, bmain, ntree);
}
static bool rna_SimulationStateItem_socket_type_supported(const EnumPropertyItem *item)
{
return NOD_geometry_simulation_output_item_socket_type_supported(
@ -4277,6 +4303,20 @@ static const EnumPropertyItem *rna_SimulationStateItem_socket_type_itemf(bContex
rna_SimulationStateItem_socket_type_supported);
}
static bool rna_RepeatItem_socket_type_supported(const EnumPropertyItem *item)
{
return NodeRepeatItem::supports_type(eNodeSocketDatatype(item->value));
}
static const EnumPropertyItem *rna_RepeatItem_socket_type_itemf(bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free)
{
*r_free = true;
return itemf_function_check(node_socket_data_type_items, rna_RepeatItem_socket_type_supported);
}
static void rna_SimulationStateItem_name_set(PointerRNA *ptr, const char *value)
{
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
@ -4288,6 +4328,14 @@ static void rna_SimulationStateItem_name_set(PointerRNA *ptr, const char *value)
NOD_geometry_simulation_output_item_set_unique_name(sim, item, value, defname);
}
static void rna_RepeatItem_name_set(PointerRNA *ptr, const char *value)
{
bNode *node = find_node_by_repeat_item(ptr);
NodeRepeatItem *item = static_cast<NodeRepeatItem *>(ptr->data);
auto *storage = static_cast<NodeGeometryRepeatOutput *>(node->storage);
storage->set_item_name(*item, value);
}
static void rna_SimulationStateItem_color_get(PointerRNA *ptr, float *values)
{
NodeSimulationItem *item = static_cast<NodeSimulationItem *>(ptr->data);
@ -4296,6 +4344,14 @@ static void rna_SimulationStateItem_color_get(PointerRNA *ptr, float *values)
ED_node_type_draw_color(socket_type_idname, values);
}
static void rna_RepeatItem_color_get(PointerRNA *ptr, float *values)
{
NodeRepeatItem *item = static_cast<NodeRepeatItem *>(ptr->data);
const char *socket_type_idname = nodeStaticSocketType(item->socket_type, 0);
ED_node_type_draw_color(socket_type_idname, values);
}
static PointerRNA rna_NodeGeometrySimulationInput_paired_output_get(PointerRNA *ptr)
{
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
@ -4306,6 +4362,17 @@ static PointerRNA rna_NodeGeometrySimulationInput_paired_output_get(PointerRNA *
return r_ptr;
}
static PointerRNA rna_NodeGeometryRepeatInput_paired_output_get(PointerRNA *ptr)
{
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode *node = static_cast<bNode *>(ptr->data);
NodeGeometryRepeatInput *storage = static_cast<NodeGeometryRepeatInput *>(node->storage);
bNode *output_node = ntree->node_by_id(storage->output_node_id);
PointerRNA r_ptr;
RNA_pointer_create(&ntree->id, &RNA_Node, output_node, &r_ptr);
return r_ptr;
}
static bool rna_GeometryNodeSimulationInput_pair_with_output(
ID *id, bNode *node, bContext *C, ReportList *reports, bNode *output_node)
{
@ -4327,6 +4394,27 @@ static bool rna_GeometryNodeSimulationInput_pair_with_output(
return true;
}
static bool rna_GeometryNodeRepeatInput_pair_with_output(
ID *id, bNode *node, bContext *C, ReportList *reports, bNode *output_node)
{
bNodeTree *ntree = (bNodeTree *)id;
if (!NOD_geometry_repeat_input_pair_with_output(ntree, node, output_node)) {
BKE_reportf(reports,
RPT_ERROR,
"Failed to pair repeat input node %s with output node %s",
node->name,
output_node->name);
return false;
}
BKE_ntree_update_tag_node_property(ntree, node);
ED_node_tree_propagate_change(C, CTX_data_main(C), ntree);
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
return true;
}
static NodeSimulationItem *rna_NodeGeometrySimulationOutput_items_new(
ID *id, bNode *node, Main *bmain, ReportList *reports, int socket_type, const char *name)
{
@ -4347,6 +4435,24 @@ static NodeSimulationItem *rna_NodeGeometrySimulationOutput_items_new(
return item;
}
static NodeRepeatItem *rna_NodeGeometryRepeatOutput_items_new(
ID *id, bNode *node, Main *bmain, ReportList *reports, int socket_type, const char *name)
{
NodeGeometryRepeatOutput *storage = static_cast<NodeGeometryRepeatOutput *>(node->storage);
NodeRepeatItem *item = storage->add_item(name, eNodeSocketDatatype(socket_type));
if (item == nullptr) {
BKE_report(reports, RPT_ERROR, "Unable to create socket");
}
else {
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(id);
BKE_ntree_update_tag_node_property(ntree, node);
ED_node_tree_propagate_change(nullptr, bmain, ntree);
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
}
return item;
}
static void rna_NodeGeometrySimulationOutput_items_remove(
ID *id, bNode *node, Main *bmain, ReportList *reports, NodeSimulationItem *item)
{
@ -4364,6 +4470,33 @@ static void rna_NodeGeometrySimulationOutput_items_remove(
}
}
static void rna_NodeGeometryRepeatOutput_items_remove(
ID *id, bNode *node, Main *bmain, ReportList *reports, NodeRepeatItem *item)
{
NodeGeometryRepeatOutput *storage = static_cast<NodeGeometryRepeatOutput *>(node->storage);
if (!storage->items_span().contains_ptr(item)) {
BKE_reportf(reports, RPT_ERROR, "Unable to locate item '%s' in node", item->name);
return;
}
const int remove_index = item - storage->items;
NodeRepeatItem *old_items = storage->items;
storage->items = MEM_cnew_array<NodeRepeatItem>(storage->items_num - 1, __func__);
std::copy_n(old_items, remove_index, storage->items);
std::copy_n(old_items + remove_index + 1,
storage->items_num - remove_index - 1,
storage->items + remove_index);
MEM_SAFE_FREE(old_items[remove_index].name);
storage->items_num--;
MEM_SAFE_FREE(old_items);
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(id);
BKE_ntree_update_tag_node_property(ntree, node);
ED_node_tree_propagate_change(nullptr, bmain, ntree);
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
}
static void rna_NodeGeometrySimulationOutput_items_clear(ID *id, bNode *node, Main *bmain)
{
NodeGeometrySimulationOutput *sim = static_cast<NodeGeometrySimulationOutput *>(node->storage);
@ -4375,6 +4508,17 @@ static void rna_NodeGeometrySimulationOutput_items_clear(ID *id, bNode *node, Ma
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
}
static void rna_NodeGeometryRepeatOutput_items_clear(ID * /*id*/, bNode *node, Main * /*bmain*/)
{
NodeGeometryRepeatOutput *storage = static_cast<NodeGeometryRepeatOutput *>(node->storage);
for (NodeRepeatItem &item : storage->items_span()) {
MEM_SAFE_FREE(item.name);
}
MEM_SAFE_FREE(storage->items);
storage->items_num = 0;
storage->active_index = 0;
}
static void rna_NodeGeometrySimulationOutput_items_move(
ID *id, bNode *node, Main *bmain, int from_index, int to_index)
{
@ -4393,6 +4537,37 @@ static void rna_NodeGeometrySimulationOutput_items_move(
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
}
static void rna_NodeGeometryRepeatOutput_items_move(
ID *id, bNode *node, Main *bmain, int from_index, int to_index)
{
NodeGeometryRepeatOutput *storage = static_cast<NodeGeometryRepeatOutput *>(node->storage);
if (from_index < 0 || from_index >= storage->items_num || to_index < 0 ||
to_index >= storage->items_num)
{
return;
}
if (from_index < to_index) {
const NodeRepeatItem tmp = storage->items[from_index];
for (int i = from_index; i < to_index; i++) {
storage->items[i] = storage->items[i + 1];
}
storage->items[to_index] = tmp;
}
else if (from_index > to_index) {
const NodeRepeatItem tmp = storage->items[from_index];
for (int i = from_index; i > to_index; i--) {
storage->items[i] = storage->items[i - 1];
}
storage->items[to_index] = tmp;
}
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(id);
BKE_ntree_update_tag_node_property(ntree, node);
ED_node_tree_propagate_change(nullptr, bmain, ntree);
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
}
static PointerRNA rna_NodeGeometrySimulationOutput_active_item_get(PointerRNA *ptr)
{
bNode *node = static_cast<bNode *>(ptr->data);
@ -4403,6 +4578,18 @@ static PointerRNA rna_NodeGeometrySimulationOutput_active_item_get(PointerRNA *p
return r_ptr;
}
static PointerRNA rna_NodeGeometryRepeatOutput_active_item_get(PointerRNA *ptr)
{
bNode *node = static_cast<bNode *>(ptr->data);
NodeGeometryRepeatOutput *storage = static_cast<NodeGeometryRepeatOutput *>(node->storage);
blender::MutableSpan<NodeRepeatItem> items = storage->items_span();
PointerRNA r_ptr{};
if (items.index_range().contains(storage->active_index)) {
RNA_pointer_create(ptr->owner_id, &RNA_RepeatItem, &items[storage->active_index], &r_ptr);
}
return r_ptr;
}
static void rna_NodeGeometrySimulationOutput_active_item_set(PointerRNA *ptr,
PointerRNA value,
ReportList * /*reports*/)
@ -4413,6 +4600,18 @@ static void rna_NodeGeometrySimulationOutput_active_item_set(PointerRNA *ptr,
static_cast<NodeSimulationItem *>(value.data));
}
static void rna_NodeGeometryRepeatOutput_active_item_set(PointerRNA *ptr,
PointerRNA value,
ReportList * /*reports*/)
{
bNode *node = static_cast<bNode *>(ptr->data);
NodeGeometryRepeatOutput *storage = static_cast<NodeGeometryRepeatOutput *>(node->storage);
NodeRepeatItem *item = static_cast<NodeRepeatItem *>(value.data);
if (storage->items_span().contains_ptr(item)) {
storage->active_index = item - storage->items;
}
}
/* ******** Node Socket Types ******** */
static PointerRNA rna_NodeOutputFile_slot_layer_get(CollectionPropertyIterator *iter)
@ -10102,6 +10301,35 @@ static void def_geo_simulation_input(StructRNA *srna)
RNA_def_function_return(func, parm);
}
static void def_geo_repeat_input(StructRNA *srna)
{
PropertyRNA *prop;
FunctionRNA *func;
PropertyRNA *parm;
RNA_def_struct_sdna_from(srna, "NodeGeometryRepeatInput", "storage");
prop = RNA_def_property(srna, "paired_output", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "Node");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_pointer_funcs(
prop, "rna_NodeGeometryRepeatInput_paired_output_get", nullptr, nullptr, nullptr);
RNA_def_property_ui_text(
prop, "Paired Output", "Repeat output node that this input node is paired with");
func = RNA_def_function(
srna, "pair_with_output", "rna_GeometryNodeRepeatInput_pair_with_output");
RNA_def_function_ui_description(func, "Pair a repeat input node with an output node.");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_REPORTS | FUNC_USE_CONTEXT);
parm = RNA_def_pointer(
func, "output_node", "GeometryNode", "Output Node", "Repeat output node to pair with");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
/* return value */
parm = RNA_def_boolean(
func, "result", false, "Result", "True if pairing the node was successful");
RNA_def_function_return(func, parm);
}
static void rna_def_simulation_state_item(BlenderRNA *brna)
{
PropertyRNA *prop;
@ -10217,6 +10445,112 @@ static void def_geo_simulation_output(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE, nullptr);
}
static void rna_def_repeat_item(BlenderRNA *brna)
{
PropertyRNA *prop;
StructRNA *srna = RNA_def_struct(brna, "RepeatItem", nullptr);
RNA_def_struct_ui_text(srna, "Repeat Item", "");
RNA_def_struct_sdna(srna, "NodeRepeatItem");
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_RepeatItem_name_set");
RNA_def_property_ui_text(prop, "Name", "");
RNA_def_struct_name_property(srna, prop);
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_RepeatItem_update");
prop = RNA_def_property(srna, "socket_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, node_socket_data_type_items);
RNA_def_property_enum_funcs(prop, nullptr, nullptr, "rna_RepeatItem_socket_type_itemf");
RNA_def_property_ui_text(prop, "Socket Type", "");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_RepeatItem_update");
prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR_GAMMA);
RNA_def_property_array(prop, 4);
RNA_def_property_float_funcs(prop, "rna_RepeatItem_color_get", nullptr, nullptr);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(
prop, "Color", "Color of the corresponding socket type in the node editor");
}
static void rna_def_geo_repeat_output_items(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *parm;
FunctionRNA *func;
srna = RNA_def_struct(brna, "NodeGeometryRepeatOutputItems", nullptr);
RNA_def_struct_sdna(srna, "bNode");
RNA_def_struct_ui_text(srna, "Items", "Collection of repeat items");
func = RNA_def_function(srna, "new", "rna_NodeGeometryRepeatOutput_items_new");
RNA_def_function_ui_description(func, "Add a item to this repeat zone");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
parm = RNA_def_enum(func,
"socket_type",
node_socket_data_type_items,
SOCK_GEOMETRY,
"Socket Type",
"Socket type of the item");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_string(func, "name", nullptr, MAX_NAME, "Name", "");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
/* return value */
parm = RNA_def_pointer(func, "item", "RepeatItem", "Item", "New item");
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "remove", "rna_NodeGeometryRepeatOutput_items_remove");
RNA_def_function_ui_description(func, "Remove an item from this repeat zone");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
parm = RNA_def_pointer(func, "item", "RepeatItem", "Item", "The item to remove");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
func = RNA_def_function(srna, "clear", "rna_NodeGeometryRepeatOutput_items_clear");
RNA_def_function_ui_description(func, "Remove all items from this repeat zone");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
func = RNA_def_function(srna, "move", "rna_NodeGeometryRepeatOutput_items_move");
RNA_def_function_ui_description(func, "Move an item to another position");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
parm = RNA_def_int(
func, "from_index", -1, 0, INT_MAX, "From Index", "Index of the item to move", 0, 10000);
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_int(
func, "to_index", -1, 0, INT_MAX, "To Index", "Target index for the item", 0, 10000);
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
}
static void def_geo_repeat_output(StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeGeometryRepeatOutput", "storage");
prop = RNA_def_property(srna, "repeat_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "items", "items_num");
RNA_def_property_struct_type(prop, "RepeatItem");
RNA_def_property_ui_text(prop, "Items", "");
RNA_def_property_srna(prop, "NodeGeometryRepeatOutputItems");
prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "active_index");
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, NC_NODE, nullptr);
prop = RNA_def_property(srna, "active_item", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "RepeatItem");
RNA_def_property_pointer_funcs(prop,
"rna_NodeGeometryRepeatOutput_active_item_get",
"rna_NodeGeometryRepeatOutput_active_item_set",
nullptr,
nullptr);
RNA_def_property_flag(prop, PROP_EDITABLE);
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;
@ -13731,7 +14065,6 @@ void RNA_def_nodetree(BlenderRNA *brna)
rna_def_compositor_node(brna);
rna_def_texture_node(brna);
rna_def_geometry_node(brna);
rna_def_simulation_state_item(brna);
rna_def_function_node(brna);
rna_def_node_socket_panel(brna);
@ -13744,6 +14077,9 @@ void RNA_def_nodetree(BlenderRNA *brna)
rna_def_texture_nodetree(brna);
rna_def_geometry_nodetree(brna);
rna_def_simulation_state_item(brna);
rna_def_repeat_item(brna);
# define DefNode(Category, ID, DefFunc, EnumName, StructName, UIName, UIDesc) \
{ \
srna = define_specific_node( \
@ -13795,6 +14131,7 @@ void RNA_def_nodetree(BlenderRNA *brna)
rna_def_cmp_output_file_slot_file(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_node_instance_hash(brna);
}

@ -3326,7 +3326,7 @@ const EnumPropertyItem *rna_SpaceSpreadsheet_attribute_domain_itemf(bContext * /
static StructRNA *rna_viewer_path_elem_refine(PointerRNA *ptr)
{
ViewerPathElem *elem = static_cast<ViewerPathElem *>(ptr->data);
switch (elem->type) {
switch (ViewerPathElemType(elem->type)) {
case VIEWER_PATH_ELEM_TYPE_ID:
return &RNA_IDViewerPathElem;
case VIEWER_PATH_ELEM_TYPE_MODIFIER:
@ -3337,6 +3337,8 @@ static StructRNA *rna_viewer_path_elem_refine(PointerRNA *ptr)
return &RNA_SimulationZoneViewerPathElem;
case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE:
return &RNA_ViewerNodeViewerPathElem;
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE:
return &RNA_RepeatZoneViewerPathElem;
}
BLI_assert_unreachable();
return nullptr;
@ -8079,6 +8081,7 @@ static const EnumPropertyItem viewer_path_elem_type_items[] = {
{VIEWER_PATH_ELEM_TYPE_GROUP_NODE, "GROUP_NODE", ICON_NONE, "Group Node", ""},
{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", ""},
{0, nullptr, 0, nullptr, nullptr},
};
@ -8146,6 +8149,17 @@ static void rna_def_simulation_zone_viewer_path_elem(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Simulation Output Node ID", "");
}
static void rna_def_repeat_zone_viewer_path_elem(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "RepeatZoneViewerPathElem", "ViewerPathElem");
prop = RNA_def_property(srna, "repeat_output_node_id", PROP_INT, PROP_NONE);
RNA_def_property_ui_text(prop, "Repeat Output Node ID", "");
}
static void rna_def_viewer_node_viewer_path_elem(BlenderRNA *brna)
{
StructRNA *srna;
@ -8167,6 +8181,7 @@ static void rna_def_viewer_path(BlenderRNA *brna)
rna_def_modifier_viewer_path_elem(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_viewer_node_viewer_path_elem(brna);
srna = RNA_def_struct(brna, "ViewerPath", nullptr);

@ -3063,6 +3063,12 @@ static void rna_def_userdef_theme_space_node(BlenderRNA *brna)
RNA_def_property_array(prop, 4);
RNA_def_property_ui_text(prop, "Simulation Zone", "");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
prop = RNA_def_property(srna, "repeat_zone", PROP_FLOAT, PROP_COLOR_GAMMA);
RNA_def_property_float_sdna(prop, NULL, "node_zone_repeat");
RNA_def_property_array(prop, 4);
RNA_def_property_ui_text(prop, "Repeat Zone", "");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
}
static void rna_def_userdef_theme_space_buts(BlenderRNA *brna)

@ -474,6 +474,27 @@ static void find_side_effect_nodes_for_viewer_path(
zone = next_zone;
break;
}
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
const auto &typed_elem = *reinterpret_cast<const RepeatZoneViewerPathElem *>(elem);
const bke::bNodeTreeZone *next_zone = tree_zones->get_zone_by_node(
typed_elem.repeat_output_node_id);
if (next_zone == nullptr) {
return;
}
if (next_zone->parent_zone != zone) {
return;
}
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
next_zone, nullptr);
if (lf_zone_node == nullptr) {
return;
}
local_side_effect_nodes.add(compute_context_builder.hash(), lf_zone_node);
compute_context_builder.push<bke::RepeatZoneComputeContext>(*next_zone->output_node,
typed_elem.iteration);
zone = next_zone;
break;
}
case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: {
const auto &typed_elem = *reinterpret_cast<const GroupNodeViewerPathElem *>(elem);
const bNode *node = group->node_by_id(typed_elem.node_id);

@ -24,6 +24,9 @@ bNode *NOD_geometry_simulation_input_get_paired_output(bNodeTree *node_tree,
bool NOD_geometry_simulation_input_pair_with_output(const bNodeTree *node_tree,
bNode *simulation_input_node,
const bNode *simulation_output_node);
bool NOD_geometry_repeat_input_pair_with_output(const bNodeTree *node_tree,
bNode *repeat_input_node,
const bNode *repeat_output_node);
/** \} */

@ -262,8 +262,8 @@ std::unique_ptr<LazyFunction> get_simulation_input_lazy_function(
GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info);
std::unique_ptr<LazyFunction> get_switch_node_lazy_function(const bNode &node);
bke::sim::SimulationZoneID get_simulation_zone_id(const GeoNodesLFUserData &user_data,
const int output_node_id);
std::optional<bke::sim::SimulationZoneID> get_simulation_zone_id(
const GeoNodesLFUserData &user_data, const int output_node_id);
/**
* An anonymous attribute created by a node.

@ -396,6 +396,8 @@ DefNode(GeometryNode, GEO_NODE_PROXIMITY, def_geo_proximity, "PROXIMITY", Proxim
DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "Cast rays from the context geometry onto a target geometry, and retrieve information from each hit point")
DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "Convert instances into real geometry data")
DefNode(GeometryNode, GEO_NODE_REMOVE_ATTRIBUTE, 0, "REMOVE_ATTRIBUTE", RemoveAttribute, "Remove Named Attribute", "Delete an attribute with a specified name from a geometry. Typically used to optimize performance")
DefNode(GeometryNode, GEO_NODE_REPEAT_INPUT, def_geo_repeat_input, "REPEAT_INPUT", RepeatInput, "Repeat Input", "")
DefNode(GeometryNode, GEO_NODE_REPEAT_OUTPUT, def_geo_repeat_output, "REPEAT_OUTPUT", RepeatOutput, "Repeat Output", "")
DefNode(GeometryNode, GEO_NODE_REPLACE_MATERIAL, 0, "REPLACE_MATERIAL", ReplaceMaterial, "Replace Material", "Swap one material with another")
DefNode(GeometryNode, GEO_NODE_RESAMPLE_CURVE, def_geo_curve_resample, "RESAMPLE_CURVE", ResampleCurve, "Resample Curve", "Generate a poly spline for each input spline")
DefNode(GeometryNode, GEO_NODE_REVERSE_CURVE, 0, "REVERSE_CURVE", ReverseCurve, "Reverse Curve", "Change the direction of curves by swapping their start and end data")

@ -148,6 +148,8 @@ set(SRC
nodes/node_geo_raycast.cc
nodes/node_geo_realize_instances.cc
nodes/node_geo_remove_attribute.cc
nodes/node_geo_repeat_input.cc
nodes/node_geo_repeat_output.cc
nodes/node_geo_rotate_instances.cc
nodes/node_geo_sample_index.cc
nodes/node_geo_sample_nearest.cc

@ -135,6 +135,8 @@ void register_geometry_nodes()
register_node_type_geo_raycast();
register_node_type_geo_realize_instances();
register_node_type_geo_remove_attribute();
register_node_type_geo_repeat_input();
register_node_type_geo_repeat_output();
register_node_type_geo_rotate_instances();
register_node_type_geo_sample_index();
register_node_type_geo_sample_nearest_surface();

@ -132,6 +132,8 @@ void register_node_type_geo_proximity();
void register_node_type_geo_raycast();
void register_node_type_geo_realize_instances();
void register_node_type_geo_remove_attribute();
void register_node_type_geo_repeat_input();
void register_node_type_geo_repeat_output();
void register_node_type_geo_rotate_instances();
void register_node_type_geo_sample_index();
void register_node_type_geo_sample_nearest_surface();

@ -160,4 +160,7 @@ void copy_with_checked_indices(const GVArray &src,
const IndexMask &mask,
GMutableSpan dst);
void socket_declarations_for_repeat_items(const Span<NodeRepeatItem> items,
NodeDeclaration &r_declaration);
} // namespace blender::nodes

@ -0,0 +1,124 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_compute_contexts.hh"
#include "BKE_scene.h"
#include "DEG_depsgraph_query.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "NOD_geometry.hh"
#include "NOD_socket.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_repeat_input_cc {
NODE_STORAGE_FUNCS(NodeGeometryRepeatInput);
static void node_declare_dynamic(const bNodeTree &tree,
const bNode &node,
NodeDeclaration &r_declaration)
{
NodeDeclarationBuilder b{r_declaration};
b.add_input<decl::Int>(N_("Iterations")).min(0).default_value(1);
const NodeGeometryRepeatInput &storage = node_storage(node);
const bNode *output_node = tree.node_by_id(storage.output_node_id);
if (output_node != nullptr) {
const NodeGeometryRepeatOutput &output_storage =
*static_cast<const NodeGeometryRepeatOutput *>(output_node->storage);
socket_declarations_for_repeat_items(output_storage.items_span(), r_declaration);
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryRepeatInput *data = MEM_cnew<NodeGeometryRepeatInput>(__func__);
/* Needs to be initialized for the node to work. */
data->output_node_id = 0;
node->storage = data;
}
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
{
const bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id);
if (!output_node) {
return true;
}
auto &storage = *static_cast<NodeGeometryRepeatOutput *>(output_node->storage);
if (link->tonode == node) {
if (link->tosock->identifier == StringRef("__extend__")) {
if (const NodeRepeatItem *item = storage.add_item(link->fromsock->name,
eNodeSocketDatatype(link->fromsock->type)))
{
update_node_declaration_and_sockets(*ntree, *node);
link->tosock = nodeFindSocket(node, SOCK_IN, item->identifier_str().c_str());
return true;
}
}
else {
return true;
}
}
if (link->fromnode == node) {
if (link->fromsock->identifier == StringRef("__extend__")) {
if (const NodeRepeatItem *item = storage.add_item(link->tosock->name,
eNodeSocketDatatype(link->tosock->type)))
{
update_node_declaration_and_sockets(*ntree, *node);
link->fromsock = nodeFindSocket(node, SOCK_OUT, item->identifier_str().c_str());
return true;
}
}
else {
return true;
}
}
return false;
}
} // namespace blender::nodes::node_geo_repeat_input_cc
void register_node_type_geo_repeat_input()
{
namespace file_ns = blender::nodes::node_geo_repeat_input_cc;
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_REPEAT_INPUT, "Repeat Input", NODE_CLASS_INTERFACE);
ntype.initfunc = file_ns::node_init;
ntype.declare_dynamic = file_ns::node_declare_dynamic;
ntype.gather_add_node_search_ops = nullptr;
ntype.gather_link_search_ops = nullptr;
ntype.insert_link = file_ns::node_insert_link;
node_type_storage(
&ntype, "NodeGeometryRepeatInput", node_free_standard_storage, node_copy_standard_storage);
nodeRegisterType(&ntype);
}
bool NOD_geometry_repeat_input_pair_with_output(const bNodeTree *node_tree,
bNode *repeat_input_node,
const bNode *repeat_output_node)
{
namespace file_ns = blender::nodes::node_geo_repeat_input_cc;
BLI_assert(repeat_input_node->type == GEO_NODE_REPEAT_INPUT);
if (repeat_output_node->type != GEO_NODE_REPEAT_OUTPUT) {
return false;
}
/* Allow only one input paired to an output. */
for (const bNode *other_input_node : node_tree->nodes_by_type("GeometryNodeRepeatInput")) {
if (other_input_node != repeat_input_node) {
const NodeGeometryRepeatInput &other_storage = file_ns::node_storage(*other_input_node);
if (other_storage.output_node_id == repeat_output_node->identifier) {
return false;
}
}
}
NodeGeometryRepeatInput &storage = file_ns::node_storage(*repeat_input_node);
storage.output_node_id = repeat_output_node->identifier;
return true;
}

@ -0,0 +1,333 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_compute_contexts.hh"
#include "BKE_scene.h"
#include "DEG_depsgraph_query.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "NOD_add_node_search.hh"
#include "NOD_geometry.hh"
#include "NOD_socket.hh"
#include "BLI_string_utils.h"
#include "node_geometry_util.hh"
namespace blender::nodes {
static std::unique_ptr<SocketDeclaration> socket_declaration_for_repeat_item(
const NodeRepeatItem &item, const eNodeSocketInOut in_out, const int corresponding_input = -1)
{
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
std::unique_ptr<SocketDeclaration> decl;
auto handle_field_decl = [&](SocketDeclaration &decl) {
if (in_out == SOCK_IN) {
decl.input_field_type = InputSocketFieldType::IsSupported;
}
else {
decl.output_field_dependency = OutputFieldDependency::ForPartiallyDependentField(
{corresponding_input});
}
};
switch (socket_type) {
case SOCK_FLOAT:
decl = std::make_unique<decl::Float>();
handle_field_decl(*decl);
break;
case SOCK_VECTOR:
decl = std::make_unique<decl::Vector>();
handle_field_decl(*decl);
break;
case SOCK_RGBA:
decl = std::make_unique<decl::Color>();
handle_field_decl(*decl);
break;
case SOCK_BOOLEAN:
decl = std::make_unique<decl::Bool>();
handle_field_decl(*decl);
break;
case SOCK_ROTATION:
decl = std::make_unique<decl::Rotation>();
handle_field_decl(*decl);
break;
case SOCK_INT:
decl = std::make_unique<decl::Int>();
handle_field_decl(*decl);
break;
case SOCK_STRING:
decl = std::make_unique<decl::String>();
break;
case SOCK_GEOMETRY:
decl = std::make_unique<decl::Geometry>();
break;
case SOCK_OBJECT:
decl = std::make_unique<decl::Object>();
break;
case SOCK_IMAGE:
decl = std::make_unique<decl::Image>();
break;
case SOCK_COLLECTION:
decl = std::make_unique<decl::Collection>();
break;
case SOCK_MATERIAL:
decl = std::make_unique<decl::Material>();
break;
default:
BLI_assert_unreachable();
break;
}
decl->name = item.name ? item.name : "";
decl->identifier = item.identifier_str();
decl->in_out = in_out;
return decl;
}
void socket_declarations_for_repeat_items(const Span<NodeRepeatItem> items,
NodeDeclaration &r_declaration)
{
for (const int i : items.index_range()) {
const NodeRepeatItem &item = items[i];
r_declaration.inputs.append(socket_declaration_for_repeat_item(item, SOCK_IN));
r_declaration.outputs.append(
socket_declaration_for_repeat_item(item, SOCK_OUT, r_declaration.inputs.size() - 1));
}
r_declaration.inputs.append(decl::create_extend_declaration(SOCK_IN));
r_declaration.outputs.append(decl::create_extend_declaration(SOCK_OUT));
}
} // namespace blender::nodes
namespace blender::nodes::node_geo_repeat_output_cc {
NODE_STORAGE_FUNCS(NodeGeometryRepeatOutput);
static void node_declare_dynamic(const bNodeTree & /*node_tree*/,
const bNode &node,
NodeDeclaration &r_declaration)
{
const NodeGeometryRepeatOutput &storage = node_storage(node);
socket_declarations_for_repeat_items(storage.items_span(), r_declaration);
}
static void search_node_add_ops(GatherAddNodeSearchParams &params)
{
AddNodeItem item;
item.ui_name = IFACE_("Repeat Zone");
item.description = TIP_("Add a new repeat input and output nodes to the node tree");
item.add_fn = [](const bContext &C, bNodeTree &node_tree, float2 cursor) {
bNode *input = nodeAddNode(&C, &node_tree, "GeometryNodeRepeatInput");
bNode *output = nodeAddNode(&C, &node_tree, "GeometryNodeRepeatOutput");
static_cast<NodeGeometryRepeatInput *>(input->storage)->output_node_id = output->identifier;
NodeRepeatItem &item = node_storage(*output).items[0];
update_node_declaration_and_sockets(node_tree, *input);
update_node_declaration_and_sockets(node_tree, *output);
const std::string identifier = item.identifier_str();
nodeAddLink(&node_tree,
input,
nodeFindSocket(input, SOCK_OUT, identifier.c_str()),
output,
nodeFindSocket(output, SOCK_IN, identifier.c_str()));
input->locx = cursor.x / UI_SCALE_FAC - 150;
input->locy = cursor.y / UI_SCALE_FAC + 20;
output->locx = cursor.x / UI_SCALE_FAC + 150;
output->locy = cursor.y / UI_SCALE_FAC + 20;
return Vector<bNode *>({input, output});
};
params.add_item(std::move(item));
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryRepeatOutput *data = MEM_cnew<NodeGeometryRepeatOutput>(__func__);
data->next_identifier = 0;
data->items = MEM_cnew_array<NodeRepeatItem>(1, __func__);
data->items[0].name = BLI_strdup(DATA_("Geometry"));
data->items[0].socket_type = SOCK_GEOMETRY;
data->items[0].identifier = data->next_identifier++;
data->items_num = 1;
node->storage = data;
}
static void node_free_storage(bNode *node)
{
NodeGeometryRepeatOutput &storage = node_storage(*node);
for (NodeRepeatItem &item : storage.items_span()) {
MEM_SAFE_FREE(item.name);
}
MEM_SAFE_FREE(storage.items);
MEM_freeN(node->storage);
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeGeometryRepeatOutput &src_storage = node_storage(*src_node);
NodeGeometryRepeatOutput *dst_storage = MEM_cnew<NodeGeometryRepeatOutput>(__func__);
dst_storage->items = MEM_cnew_array<NodeRepeatItem>(src_storage.items_num, __func__);
dst_storage->items_num = src_storage.items_num;
dst_storage->active_index = src_storage.active_index;
dst_storage->next_identifier = src_storage.next_identifier;
for (const int i : IndexRange(src_storage.items_num)) {
if (char *name = src_storage.items[i].name) {
dst_storage->items[i].identifier = src_storage.items[i].identifier;
dst_storage->items[i].name = BLI_strdup(name);
dst_storage->items[i].socket_type = src_storage.items[i].socket_type;
}
}
dst_node->storage = dst_storage;
}
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
{
NodeGeometryRepeatOutput &storage = node_storage(*node);
if (link->tonode == node) {
if (link->tosock->identifier == StringRef("__extend__")) {
if (const NodeRepeatItem *item = storage.add_item(link->fromsock->name,
eNodeSocketDatatype(link->fromsock->type)))
{
update_node_declaration_and_sockets(*ntree, *node);
link->tosock = nodeFindSocket(node, SOCK_IN, item->identifier_str().c_str());
return true;
}
}
else {
return true;
}
}
if (link->fromnode == node) {
if (link->fromsock->identifier == StringRef("__extend__")) {
if (const NodeRepeatItem *item = storage.add_item(link->tosock->name,
eNodeSocketDatatype(link->tosock->type)))
{
update_node_declaration_and_sockets(*ntree, *node);
link->fromsock = nodeFindSocket(node, SOCK_OUT, item->identifier_str().c_str());
return true;
}
}
else {
return true;
}
}
return false;
}
} // namespace blender::nodes::node_geo_repeat_output_cc
blender::Span<NodeRepeatItem> NodeGeometryRepeatOutput::items_span() const
{
return blender::Span<NodeRepeatItem>(items, items_num);
}
blender::MutableSpan<NodeRepeatItem> NodeGeometryRepeatOutput::items_span()
{
return blender::MutableSpan<NodeRepeatItem>(items, items_num);
}
bool NodeRepeatItem::supports_type(const eNodeSocketDatatype type)
{
return ELEM(type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_INT,
SOCK_STRING,
SOCK_GEOMETRY,
SOCK_OBJECT,
SOCK_MATERIAL,
SOCK_IMAGE,
SOCK_COLLECTION);
}
std::string NodeRepeatItem::identifier_str() const
{
return "Item_" + std::to_string(this->identifier);
}
NodeRepeatItem *NodeGeometryRepeatOutput::add_item(const char *name,
const eNodeSocketDatatype type)
{
if (!NodeRepeatItem::supports_type(type)) {
return nullptr;
}
const int insert_index = this->items_num;
NodeRepeatItem *old_items = this->items;
this->items = MEM_cnew_array<NodeRepeatItem>(this->items_num + 1, __func__);
std::copy_n(old_items, insert_index, this->items);
NodeRepeatItem &new_item = this->items[insert_index];
std::copy_n(old_items + insert_index + 1,
this->items_num - insert_index,
this->items + insert_index + 1);
new_item.identifier = this->next_identifier++;
this->set_item_name(new_item, name);
new_item.socket_type = type;
this->items_num++;
MEM_SAFE_FREE(old_items);
return &new_item;
}
void NodeGeometryRepeatOutput::set_item_name(NodeRepeatItem &item, const char *name)
{
char unique_name[MAX_NAME + 4];
STRNCPY(unique_name, name);
struct Args {
NodeGeometryRepeatOutput *storage;
const NodeRepeatItem *item;
} args = {this, &item};
const char *default_name = nodeStaticSocketLabel(item.socket_type, 0);
BLI_uniquename_cb(
[](void *arg, const char *name) {
const Args &args = *static_cast<Args *>(arg);
for (const NodeRepeatItem &item : args.storage->items_span()) {
if (&item != args.item) {
if (STREQ(item.name, name)) {
return true;
}
}
}
return false;
},
&args,
default_name,
'.',
unique_name,
ARRAY_SIZE(unique_name));
MEM_SAFE_FREE(item.name);
item.name = BLI_strdup(unique_name);
}
void register_node_type_geo_repeat_output()
{
namespace file_ns = blender::nodes::node_geo_repeat_output_cc;
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_REPEAT_OUTPUT, "Repeat Output", NODE_CLASS_INTERFACE);
ntype.initfunc = file_ns::node_init;
ntype.declare_dynamic = file_ns::node_declare_dynamic;
ntype.gather_add_node_search_ops = file_ns::search_node_add_ops;
ntype.insert_link = file_ns::node_insert_link;
node_type_storage(
&ntype, "NodeGeometryRepeatOutput", file_ns::node_free_storage, file_ns::node_copy_storage);
nodeRegisterType(&ntype);
}

@ -70,12 +70,17 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction {
params.set_output(0, fn::ValueOrField<float>(delta_time));
}
const bke::sim::SimulationZoneID zone_id = get_simulation_zone_id(user_data, output_node_id_);
const std::optional<bke::sim::SimulationZoneID> zone_id = get_simulation_zone_id(
user_data, output_node_id_);
if (!zone_id) {
params.set_default_remaining_outputs();
return;
}
/* When caching is turned off and the old state doesn't need to persist, moving data
* from the last state instead of copying it can avoid copies of geometry data arrays. */
if (auto *state = modifier_data.prev_simulation_state_mutable) {
if (bke::sim::SimulationZoneState *zone = state->get_zone_state(zone_id)) {
if (bke::sim::SimulationZoneState *zone = state->get_zone_state(*zone_id)) {
this->output_simulation_state_move(params, user_data, *zone);
return;
}
@ -83,7 +88,7 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction {
/* If there is a read-only state from the last frame, output that directly. */
if (const auto *state = modifier_data.prev_simulation_state) {
if (const bke::sim::SimulationZoneState *zone = state->get_zone_state(zone_id)) {
if (const bke::sim::SimulationZoneState *zone = state->get_zone_state(*zone_id)) {
this->output_simulation_state_copy(params, user_data, *zone);
return;
}

@ -777,11 +777,16 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
EvalData &eval_data = *static_cast<EvalData *>(context.storage);
BLI_SCOPED_DEFER([&]() { eval_data.is_first_evaluation = false; });
const bke::sim::SimulationZoneID zone_id = get_simulation_zone_id(user_data, node_.identifier);
const std::optional<bke::sim::SimulationZoneID> zone_id = get_simulation_zone_id(
user_data, node_.identifier);
if (!zone_id) {
params.set_default_remaining_outputs();
return;
}
const bke::sim::SimulationZoneState *current_zone_state =
modifier_data.current_simulation_state ?
modifier_data.current_simulation_state->get_zone_state(zone_id) :
modifier_data.current_simulation_state->get_zone_state(*zone_id) :
nullptr;
if (eval_data.is_first_evaluation && current_zone_state != nullptr) {
/* Common case when data is cached already. */
@ -792,7 +797,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
if (modifier_data.current_simulation_state_for_write == nullptr) {
const bke::sim::SimulationZoneState *prev_zone_state =
modifier_data.prev_simulation_state ?
modifier_data.prev_simulation_state->get_zone_state(zone_id) :
modifier_data.prev_simulation_state->get_zone_state(*zone_id) :
nullptr;
if (prev_zone_state == nullptr) {
/* There is no previous simulation state and we also don't create a new one, so just
@ -802,7 +807,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
}
const bke::sim::SimulationZoneState *next_zone_state =
modifier_data.next_simulation_state ?
modifier_data.next_simulation_state->get_zone_state(zone_id) :
modifier_data.next_simulation_state->get_zone_state(*zone_id) :
nullptr;
if (next_zone_state == nullptr) {
/* Output the last cached simulation state. */
@ -820,7 +825,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
}
bke::sim::SimulationZoneState &new_zone_state =
modifier_data.current_simulation_state_for_write->get_zone_state_for_write(zone_id);
modifier_data.current_simulation_state_for_write->get_zone_state_for_write(*zone_id);
if (eval_data.is_first_evaluation) {
new_zone_state.item_by_identifier.clear();
}
@ -906,8 +911,8 @@ std::unique_ptr<LazyFunction> get_simulation_output_lazy_function(
return std::make_unique<file_ns::LazyFunctionForSimulationOutputNode>(node, own_lf_graph_info);
}
bke::sim::SimulationZoneID get_simulation_zone_id(const GeoNodesLFUserData &user_data,
const int output_node_id)
std::optional<bke::sim::SimulationZoneID> get_simulation_zone_id(
const GeoNodesLFUserData &user_data, const int output_node_id)
{
Vector<int> node_ids;
for (const ComputeContext *context = user_data.compute_context; context != nullptr;
@ -916,6 +921,10 @@ bke::sim::SimulationZoneID get_simulation_zone_id(const GeoNodesLFUserData &user
if (const auto *node_context = dynamic_cast<const bke::NodeGroupComputeContext *>(context)) {
node_ids.append(node_context->node_id());
}
else if (dynamic_cast<const bke::RepeatZoneComputeContext *>(context) != nullptr) {
/* Simulation can't be used in a repeat zone. */
return std::nullopt;
}
}
std::reverse(node_ids.begin(), node_ids.end());
node_ids.append(output_node_id);

@ -43,6 +43,7 @@
#include "BKE_type_conversions.hh"
#include "FN_field_cpp_type.hh"
#include "FN_lazy_function_execute.hh"
#include "FN_lazy_function_graph_executor.hh"
#include "DEG_depsgraph_query.h"
@ -1421,6 +1422,300 @@ struct ZoneBuildInfo {
Map<int, int> attribute_set_input_by_caller_propagation_index;
};
/**
* Describes what the individual inputs and outputs of the #LazyFunction mean that's created for
* the repeat body.
*/
struct RepeatBodyIndices {
IndexRange main_inputs;
IndexRange main_outputs;
IndexRange border_link_inputs;
IndexRange main_input_usages;
IndexRange main_output_usages;
IndexRange border_link_usages;
/**
* Some anonymous attribute sets are input into the repeat body from the outside. These two maps
* indicate which repeat body input corresponds to attribute set. Attribute sets are identified
* by either a "field source index" or "caller propagation index".
*/
Map<int, int> attribute_set_input_by_field_source_index;
Map<int, int> attribute_set_input_by_caller_propagation_index;
};
class LazyFunctionForRepeatZone : public LazyFunction {
private:
const bNodeTreeZone &zone_;
const bNode &repeat_output_bnode_;
const ZoneBuildInfo &zone_info_;
const LazyFunction &body_fn_;
const RepeatBodyIndices &body_indices_;
public:
LazyFunctionForRepeatZone(const bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const LazyFunction &body_fn,
const RepeatBodyIndices &body_indices)
: zone_(zone),
repeat_output_bnode_(*zone.output_node),
zone_info_(zone_info),
body_fn_(body_fn),
body_indices_(body_indices)
{
debug_name_ = "Repeat Zone";
for (const bNodeSocket *socket : zone.input_node->input_sockets().drop_back(1)) {
inputs_.append_as(socket->name, *socket->typeinfo->geometry_nodes_cpp_type);
}
zone_info.main_input_indices = inputs_.index_range();
for (const bNodeLink *link : zone.border_links) {
inputs_.append_as(link->fromsock->name, *link->tosock->typeinfo->geometry_nodes_cpp_type);
}
zone_info.border_link_input_indices = 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>());
outputs_.append_as(socket->name, *socket->typeinfo->geometry_nodes_cpp_type);
}
zone_info.main_output_usage_indices = inputs_.index_range().take_back(
zone.output_node->output_sockets().drop_back(1).size());
zone_info.main_output_indices = 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.main_input_usage_indices = 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.border_link_input_usage_indices = outputs_.index_range().take_back(
zone.border_links.size());
for (const auto item : body_indices.attribute_set_input_by_field_source_index.items()) {
const int index = inputs_.append_and_get_index_as(
"Attribute Set", CPPType::get<bke::AnonymousAttributeSet>());
zone_info.attribute_set_input_by_field_source_index.add_new(item.key, index);
}
for (const auto item : body_indices.attribute_set_input_by_caller_propagation_index.items()) {
const int index = inputs_.append_and_get_index_as(
"Attribute Set", CPPType::get<bke::AnonymousAttributeSet>());
zone_info.attribute_set_input_by_caller_propagation_index.add_new(item.key, index);
}
}
void execute_impl(lf::Params &params, const lf::Context &context) const override
{
GeoNodesLFUserData &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
const NodeGeometryRepeatOutput &node_storage = *static_cast<const NodeGeometryRepeatOutput *>(
repeat_output_bnode_.storage);
/* Number of iterations to evaluate. */
const int iterations = std::max<int>(
0, params.get_input<ValueOrField<int>>(zone_info_.main_input_indices[0]).as_value());
const int repeat_items_num = node_storage.items_num;
/* Gather data types of the repeat items. */
Array<const CPPType *> repeat_item_types(repeat_items_num);
for (const int i : body_indices_.main_inputs.index_range()) {
const int input_i = body_indices_.main_inputs[i];
const CPPType &type = *body_fn_.inputs()[input_i].type;
repeat_item_types[i] = &type;
}
LinearAllocator<> allocator;
Array<void *, 64> repeat_item_values((iterations + 1) * repeat_items_num, nullptr);
/* Allocate memory for the looped values. */
for (const int iteration : IndexRange(iterations)) {
MutableSpan<void *> item_values = repeat_item_values.as_mutable_span().slice(
(iteration + 1) * repeat_items_num, repeat_items_num);
for (const int item_i : IndexRange(repeat_items_num)) {
const CPPType &type = *repeat_item_types[item_i];
void *buffer = allocator.allocate(type.size(), type.alignment());
item_values[item_i] = buffer;
}
}
/* Load the inputs of the first repeat iteration. */
MutableSpan<void *> first_item_values = repeat_item_values.as_mutable_span().take_front(
repeat_items_num);
for (const int i : IndexRange(repeat_items_num)) {
/* +1 because of the iterations input. */
const int input_index = zone_info_.main_input_indices[i + 1];
void *value = params.try_get_input_data_ptr(input_index);
BLI_assert(value != nullptr);
first_item_values[i] = value;
}
/* Load border link values. */
const int border_links_num = zone_info_.border_link_input_indices.size();
Array<void *> border_link_input_values(border_links_num, nullptr);
for (const int i : IndexRange(border_links_num)) {
const int input_index = zone_info_.border_link_input_indices[i];
void *value = params.try_get_input_data_ptr(input_index);
BLI_assert(value != nullptr);
border_link_input_values[i] = value;
}
/* Load attribute sets that are needed to propagate attributes correctly in the zone. */
Map<int, bke::AnonymousAttributeSet *> attribute_set_by_field_source_index;
Map<int, bke::AnonymousAttributeSet *> attribute_set_by_caller_propagation_index;
for (const auto item : zone_info_.attribute_set_input_by_field_source_index.items()) {
bke::AnonymousAttributeSet &attribute_set = params.get_input<bke::AnonymousAttributeSet>(
item.value);
attribute_set_by_field_source_index.add_new(item.key, &attribute_set);
}
for (const auto item : zone_info_.attribute_set_input_by_caller_propagation_index.items()) {
bke::AnonymousAttributeSet &attribute_set = params.get_input<bke::AnonymousAttributeSet>(
item.value);
attribute_set_by_caller_propagation_index.add_new(item.key, &attribute_set);
}
const int body_inputs_num = body_fn_.inputs().size();
const int body_outputs_num = body_fn_.outputs().size();
/* Evaluate the repeat zone eagerly, one iteration at a time.
* This can be made more lazy as a separate step. */
for (const int iteration : IndexRange(iterations)) {
/* Prepare all data that has to be passed into the evaluation of the repeat zone body. */
Array<GMutablePointer> inputs(body_inputs_num);
Array<GMutablePointer> outputs(body_outputs_num);
Array<std::optional<lf::ValueUsage>> input_usages(body_inputs_num);
Array<lf::ValueUsage> output_usages(body_outputs_num, lf::ValueUsage::Used);
Array<bool> set_outputs(body_outputs_num, false);
/* Prepare pointers to the main input and output values of the repeat zone,
* as well as their usages. */
Array<bool> tmp_main_input_usages(repeat_items_num);
for (const int i : IndexRange(repeat_items_num)) {
const CPPType &type = *repeat_item_types[i];
void *prev_value = repeat_item_values[iteration * repeat_items_num + i];
void *next_value = repeat_item_values[(iteration + 1) * repeat_items_num + i];
inputs[body_indices_.main_inputs[i]] = {type, prev_value};
outputs[body_indices_.main_outputs[i]] = {type, next_value};
outputs[body_indices_.main_input_usages[i]] = &tmp_main_input_usages[i];
}
static bool static_true = true;
for (const int input_index : body_indices_.main_output_usages) {
/* All main outputs are used currently. */
inputs[input_index] = &static_true;
}
/* Prepare border link values for the repeat body. */
Array<bool> tmp_border_link_usages(border_links_num);
for (const int i : IndexRange(border_links_num)) {
const int input_index = body_indices_.border_link_inputs[i];
const int usage_index = body_indices_.border_link_usages[i];
const CPPType &type = *body_fn_.inputs()[input_index].type;
/* Need to copy because a lazy function is allowed to modify the input (e.g. move from
* it). */
void *value_copy = allocator.allocate(type.size(), type.alignment());
type.copy_construct(border_link_input_values[i], value_copy);
inputs[input_index] = {type, value_copy};
outputs[usage_index] = &tmp_border_link_usages[i];
}
/* Prepare attribute sets that are passed into the repeat body. */
for (const auto item : body_indices_.attribute_set_input_by_field_source_index.items()) {
bke::AnonymousAttributeSet &attribute_set =
*allocator
.construct<bke::AnonymousAttributeSet>(
*attribute_set_by_field_source_index.lookup(item.key))
.release();
inputs[item.value] = &attribute_set;
}
for (const auto item : body_indices_.attribute_set_input_by_caller_propagation_index.items())
{
bke::AnonymousAttributeSet &attribute_set =
*allocator
.construct<bke::AnonymousAttributeSet>(
*attribute_set_by_caller_propagation_index.lookup(item.key))
.release();
inputs[item.value] = &attribute_set;
}
/* Prepare evaluation context for the repeat body. */
bke::RepeatZoneComputeContext body_compute_context{
user_data.compute_context, repeat_output_bnode_, iteration};
GeoNodesLFUserData body_user_data = user_data;
body_user_data.compute_context = &body_compute_context;
if (user_data.modifier_data->socket_log_contexts) {
body_user_data.log_socket_values = user_data.modifier_data->socket_log_contexts->contains(
body_compute_context.hash());
}
GeoNodesLFLocalUserData body_local_user_data{body_user_data};
void *body_storage = body_fn_.init_storage(allocator);
lf::Context body_context{body_storage, &body_user_data, &body_local_user_data};
lf::BasicParams body_params{
body_fn_, inputs, outputs, input_usages, output_usages, set_outputs};
/* Actually evaluate the repeat body. */
body_fn_.execute(body_params, body_context);
/* Destruct values that are not needed after the evaluation anymore. */
body_fn_.destruct_storage(body_storage);
for (const int i : body_indices_.border_link_inputs) {
inputs[i].destruct();
}
for (const int i : body_indices_.attribute_set_input_by_field_source_index.values()) {
inputs[i].destruct();
}
for (const int i : body_indices_.attribute_set_input_by_caller_propagation_index.values()) {
inputs[i].destruct();
}
}
/* Set outputs of the repeat zone. */
for (const int i : IndexRange(repeat_items_num)) {
void *computed_value = repeat_item_values[iterations * repeat_items_num + i];
const int output_index = zone_info_.main_output_indices[i];
void *r_value = params.get_output_data_ptr(output_index);
const CPPType &type = *repeat_item_types[i];
type.move_construct(computed_value, r_value);
params.output_set(output_index);
}
for (const int i : zone_info_.main_input_usage_indices) {
params.set_output(i, true);
}
for (const int i : IndexRange(border_links_num)) {
params.set_output(zone_info_.border_link_input_usage_indices[i], true);
}
/* Destruct remaining values. */
for (const int iteration : IndexRange(iterations)) {
MutableSpan<void *> item_values = repeat_item_values.as_mutable_span().slice(
(iteration + 1) * repeat_items_num, repeat_items_num);
for (const int item_i : IndexRange(repeat_items_num)) {
const CPPType &type = *repeat_item_types[item_i];
type.destruct(item_values[item_i]);
}
}
}
std::string input_name(const int i) const override
{
if (zone_info_.main_output_usage_indices.contains(i)) {
const bNodeSocket &bsocket = zone_.output_node->output_socket(
i - zone_info_.main_output_usage_indices.first());
return "Usage: " + StringRef(bsocket.name);
}
return inputs_[i].debug_name;
}
std::string output_name(const int i) const override
{
if (zone_info_.main_input_usage_indices.contains(i)) {
const bNodeSocket &bsocket = zone_.input_node->input_socket(
i - zone_info_.main_input_usage_indices.first());
return "Usage: " + StringRef(bsocket.name);
}
return outputs_[i].debug_name;
}
};
/**
* Utility class to build a lazy-function graph based on a geometry nodes tree.
* This is mainly a separate class because it makes it easier to have variables that can be
@ -1446,7 +1741,7 @@ struct GeometryNodesLazyFunctionGraphBuilder {
Map<const bNode *, lf::Node *> simulation_inputs_usage_nodes_;
const bNodeTreeZones *tree_zones_;
Array<ZoneBuildInfo> zone_build_infos_;
MutableSpan<ZoneBuildInfo> zone_build_infos_;
friend class UsedSocketVisualizeOptions;
@ -1492,14 +1787,27 @@ struct GeometryNodesLazyFunctionGraphBuilder {
*/
void build_zone_functions()
{
zone_build_infos_.reinitialize(tree_zones_->zones.size());
zone_build_infos_ = scope_.linear_allocator().construct_array<ZoneBuildInfo>(
tree_zones_->zones.size());
const Array<int> zone_build_order = this->compute_zone_build_order();
for (const int zone_i : zone_build_order) {
const bNodeTreeZone &zone = *tree_zones_->zones[zone_i];
BLI_assert(zone.output_node->type == GEO_NODE_SIMULATION_OUTPUT);
this->build_simulation_zone_function(zone);
switch (zone.output_node->type) {
case GEO_NODE_SIMULATION_OUTPUT: {
this->build_simulation_zone_function(zone);
break;
}
case GEO_NODE_REPEAT_OUTPUT: {
this->build_repeat_zone_function(zone);
break;
}
default: {
BLI_assert_unreachable();
break;
}
}
}
}
@ -1605,10 +1913,23 @@ struct GeometryNodesLazyFunctionGraphBuilder {
Map<int, lf::OutputSocket *> lf_attribute_set_by_field_source_index;
Map<int, lf::OutputSocket *> lf_attribute_set_by_caller_propagation_index;
this->build_attribute_set_inputs_for_zone(graph_params,
zone_info,
lf_attribute_set_by_field_source_index,
lf_attribute_set_by_caller_propagation_index,
lf_zone_inputs);
lf_attribute_set_by_caller_propagation_index);
for (const auto item : lf_attribute_set_by_field_source_index.items()) {
lf::OutputSocket &lf_attribute_set_socket = *item.value;
if (lf_attribute_set_socket.node().is_dummy()) {
const int zone_input_index = lf_zone_inputs.append_and_get_index(&lf_attribute_set_socket);
zone_info.attribute_set_input_by_field_source_index.add_new(item.key, zone_input_index);
}
}
for (const auto item : lf_attribute_set_by_caller_propagation_index.items()) {
lf::OutputSocket &lf_attribute_set_socket = *item.value;
if (lf_attribute_set_socket.node().is_dummy()) {
const int zone_input_index = lf_zone_inputs.append_and_get_index(&lf_attribute_set_socket);
zone_info.attribute_set_input_by_caller_propagation_index.add_new(item.key,
zone_input_index);
}
}
this->link_attribute_set_inputs(lf_graph,
graph_params,
lf_attribute_set_by_field_source_index,
@ -1643,6 +1964,135 @@ struct GeometryNodesLazyFunctionGraphBuilder {
// std::cout << "\n\n" << lf_graph.to_dot() << "\n\n";
}
/**
* Builds a #LazyFunction for a repeat zone. For that it first builds a lazy-function graph
* from all the nodes in the zone, and then wraps that in another lazy-function that implements
* the repeating behavior.
*/
void build_repeat_zone_function(const bNodeTreeZone &zone)
{
ZoneBuildInfo &zone_info = zone_build_infos_[zone.index];
lf::Graph &lf_body_graph = scope_.construct<lf::Graph>();
BuildGraphParams graph_params{lf_body_graph};
Vector<const lf::OutputSocket *, 16> lf_body_inputs;
Vector<const lf::InputSocket *, 16> lf_body_outputs;
RepeatBodyIndices &body_indices = scope_.construct<RepeatBodyIndices>();
lf::DummyNode &lf_main_input_node = this->build_dummy_node_for_sockets(
"Repeat Input", {}, zone.input_node->output_sockets().drop_back(1), lf_body_graph);
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_socket = lf_main_input_node.output(i);
graph_params.lf_output_by_bsocket.add_new(&bsocket, &lf_socket);
}
lf_body_inputs.extend(lf_main_input_node.outputs());
body_indices.main_inputs = lf_body_inputs.index_range();
lf::DummyNode &lf_main_output_node = this->build_dummy_node_for_sockets(
"Repeat Output", zone.output_node->input_sockets().drop_back(1), {}, lf_body_graph);
lf_body_outputs.extend(lf_main_output_node.inputs());
body_indices.main_outputs = lf_body_outputs.index_range();
lf::Node &lf_main_input_usage_node = this->build_dummy_node_for_socket_usages(
"Input Usages", zone.input_node->output_sockets().drop_back(1), {}, lf_body_graph);
lf_body_outputs.extend(lf_main_input_usage_node.inputs());
body_indices.main_input_usages = lf_body_outputs.index_range().take_back(
lf_main_input_usage_node.inputs().size());
lf::Node &lf_main_output_usage_node = this->build_dummy_node_for_socket_usages(
"Output Usages", {}, zone.output_node->input_sockets().drop_back(1), lf_body_graph);
lf_body_inputs.extend(lf_main_output_usage_node.outputs());
body_indices.main_output_usages = lf_body_inputs.index_range().take_back(
lf_main_output_usage_node.outputs().size());
for (const int i : zone.output_node->input_sockets().drop_back(1).index_range()) {
const bNodeSocket &bsocket = zone.output_node->input_socket(i);
lf::InputSocket &lf_socket = lf_main_output_node.input(i);
lf::OutputSocket &lf_usage = lf_main_output_usage_node.output(i);
graph_params.lf_inputs_by_bsocket.add(&bsocket, &lf_socket);
graph_params.usage_by_bsocket.add(&bsocket, &lf_usage);
}
lf::Node &lf_border_link_input_node = this->build_zone_border_links_input_node(zone,
lf_body_graph);
lf_body_inputs.extend(lf_border_link_input_node.outputs());
body_indices.border_link_inputs = lf_body_inputs.index_range().take_back(
lf_border_link_input_node.outputs().size());
lf::Node &lf_border_link_usage_node = this->build_border_link_input_usage_node(zone,
lf_body_graph);
lf_body_outputs.extend(lf_border_link_usage_node.inputs());
body_indices.border_link_usages = lf_body_outputs.index_range().take_back(
lf_border_link_usage_node.inputs().size());
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::InputSocket &lf_usage_output = lf_main_input_usage_node.input(i);
if (lf_usage) {
lf_body_graph.add_link(*lf_usage, lf_usage_output);
}
else {
static const bool static_false = false;
lf_usage_output.set_default_value(&static_false);
}
}
for (const auto item : graph_params.lf_output_by_bsocket.items()) {
this->insert_links_from_socket(*item.key, *item.value, graph_params);
}
this->link_border_link_inputs_and_usages(
zone, lf_border_link_input_node, lf_border_link_usage_node, graph_params);
this->add_default_inputs(graph_params);
Map<int, lf::OutputSocket *> lf_attribute_set_by_field_source_index;
Map<int, lf::OutputSocket *> lf_attribute_set_by_caller_propagation_index;
this->build_attribute_set_inputs_for_zone(graph_params,
lf_attribute_set_by_field_source_index,
lf_attribute_set_by_caller_propagation_index);
for (const auto item : lf_attribute_set_by_field_source_index.items()) {
lf::OutputSocket &lf_attribute_set_socket = *item.value;
if (lf_attribute_set_socket.node().is_dummy()) {
const int body_input_index = lf_body_inputs.append_and_get_index(&lf_attribute_set_socket);
body_indices.attribute_set_input_by_field_source_index.add_new(item.key, body_input_index);
}
}
for (const auto item : lf_attribute_set_by_caller_propagation_index.items()) {
lf::OutputSocket &lf_attribute_set_socket = *item.value;
if (lf_attribute_set_socket.node().is_dummy()) {
const int body_input_index = lf_body_inputs.append_and_get_index(&lf_attribute_set_socket);
body_indices.attribute_set_input_by_caller_propagation_index.add_new(item.key,
body_input_index);
}
}
this->link_attribute_set_inputs(lf_body_graph,
graph_params,
lf_attribute_set_by_field_source_index,
lf_attribute_set_by_caller_propagation_index);
this->fix_link_cycles(lf_body_graph, graph_params.socket_usage_inputs);
lf_body_graph.update_node_indices();
auto &logger = scope_.construct<GeometryNodesLazyFunctionLogger>(*lf_graph_info_);
auto &side_effect_provider = scope_.construct<GeometryNodesLazyFunctionSideEffectProvider>();
LazyFunction &body_graph_fn = scope_.construct<lf::GraphExecutor>(
lf_body_graph, lf_body_inputs, lf_body_outputs, &logger, &side_effect_provider);
// std::cout << "\n\n" << lf_body_graph.to_dot() << "\n\n";
auto &fn = scope_.construct<LazyFunctionForRepeatZone>(
zone, zone_info, body_graph_fn, body_indices);
zone_info.lazy_function = &fn;
}
lf::DummyNode &build_zone_border_links_input_node(const bNodeTreeZone &zone, lf::Graph &lf_graph)
{
auto &debug_info = scope_.construct<lf::SimpleDummyDebugInfo>();
@ -1669,10 +2119,8 @@ struct GeometryNodesLazyFunctionGraphBuilder {
void build_attribute_set_inputs_for_zone(
BuildGraphParams &graph_params,
ZoneBuildInfo &zone_info,
Map<int, lf::OutputSocket *> &lf_attribute_set_by_field_source_index,
Map<int, lf::OutputSocket *> &lf_attribute_set_by_caller_propagation_index,
Vector<const lf::OutputSocket *, 16> &lf_zone_inputs)
Map<int, lf::OutputSocket *> &lf_attribute_set_by_caller_propagation_index)
{
const Vector<int> all_required_field_sources = this->find_all_required_field_source_indices(
graph_params.lf_attribute_set_input_by_output_geometry_bsocket,
@ -1730,10 +2178,6 @@ struct GeometryNodesLazyFunctionGraphBuilder {
const int attribute_set_index = item.value;
lf::OutputSocket &lf_attribute_set_socket = node.output(attribute_set_index);
lf_attribute_set_by_field_source_index.add(field_source_index, &lf_attribute_set_socket);
const int zone_input_index = lf_zone_inputs.append_and_get_index(&lf_attribute_set_socket);
zone_info.attribute_set_input_by_field_source_index.add(field_source_index,
zone_input_index);
}
for (const int i : all_required_caller_propagation_indices.index_range()) {
const int caller_propagation_index = all_required_caller_propagation_indices[i];
@ -1741,9 +2185,6 @@ struct GeometryNodesLazyFunctionGraphBuilder {
input_by_field_source_index.size() + i);
lf_attribute_set_by_caller_propagation_index.add_new(caller_propagation_index,
&lf_attribute_set_socket);
const int zone_input_index = lf_zone_inputs.append_and_get_index(&lf_attribute_set_socket);
zone_info.attribute_set_input_by_caller_propagation_index.add(caller_propagation_index,
zone_input_index);
}
}
}
@ -2151,6 +2592,7 @@ struct GeometryNodesLazyFunctionGraphBuilder {
const int caller_propagation_index = item.key;
const int child_zone_input_index = item.value;
lf::InputSocket &lf_attribute_set_input = child_zone_node.input(child_zone_input_index);
BLI_assert(lf_attribute_set_input.type().is<bke::AnonymousAttributeSet>());
graph_params.lf_attribute_set_input_by_caller_propagation_index.add(caller_propagation_index,
&lf_attribute_set_input);
}
@ -2818,11 +3260,11 @@ struct GeometryNodesLazyFunctionGraphBuilder {
Vector<const CPPType *, 16> output_types;
for (const bNodeSocket *bsocket : input_bsockets) {
input_types.append(bsocket->typeinfo->geometry_nodes_cpp_type);
debug_info.input_names.append(bsocket->identifier);
debug_info.input_names.append(bsocket->name);
}
for (const bNodeSocket *bsocket : output_bsockets) {
output_types.append(bsocket->typeinfo->geometry_nodes_cpp_type);
debug_info.output_names.append(bsocket->identifier);
debug_info.output_names.append(bsocket->name);
}
lf::DummyNode &node = lf_graph.add_dummy(input_types, output_types, &debug_info);
return node;
@ -2840,10 +3282,10 @@ struct GeometryNodesLazyFunctionGraphBuilder {
Vector<const CPPType *, 16> input_types(input_bsockets.size(), &bool_cpp_type);
Vector<const CPPType *, 16> output_types(output_bsockets.size(), &bool_cpp_type);
for (const bNodeSocket *bsocket : input_bsockets) {
debug_info.input_names.append(bsocket->identifier);
debug_info.input_names.append(bsocket->name);
}
for (const bNodeSocket *bsocket : output_bsockets) {
debug_info.output_names.append(bsocket->identifier);
debug_info.output_names.append(bsocket->name);
}
lf::DummyNode &node = lf_graph.add_dummy(input_types, output_types, &debug_info);
return node;
@ -3349,6 +3791,12 @@ const GeometryNodesLazyFunctionGraphInfo *ensure_geometry_nodes_lazy_function_gr
if (tree_zones == nullptr) {
return nullptr;
}
for (const std::unique_ptr<bNodeTreeZone> &zone : tree_zones->zones) {
if (zone->input_node == nullptr || zone->output_node == nullptr) {
/* Simulations and repeats need input and output nodes. */
return nullptr;
}
}
if (const ID *id_orig = DEG_get_original_id(const_cast<ID *>(&btree.id))) {
if (id_orig->tag & LIB_TAG_MISSING) {
return nullptr;

@ -527,7 +527,18 @@ static void find_tree_zone_hash_recursive(
ComputeContextBuilder &compute_context_builder,
Map<const bNodeTreeZone *, ComputeContextHash> &r_hash_by_zone)
{
compute_context_builder.push<bke::SimulationZoneComputeContext>(*zone.output_node);
switch (zone.output_node->type) {
case GEO_NODE_SIMULATION_OUTPUT: {
compute_context_builder.push<bke::SimulationZoneComputeContext>(*zone.output_node);
break;
}
case GEO_NODE_REPEAT_OUTPUT: {
/* Only show data from the first iteration for now. */
const int iteration = 0;
compute_context_builder.push<bke::RepeatZoneComputeContext>(*zone.output_node, iteration);
break;
}
}
r_hash_by_zone.add_new(&zone, compute_context_builder.hash());
for (const bNodeTreeZone *child_zone : zone.child_zones) {
find_tree_zone_hash_recursive(*child_zone, compute_context_builder, r_hash_by_zone);
@ -560,7 +571,19 @@ Map<const bNodeTreeZone *, ComputeContextHash> GeoModifierLog::
const Vector<const bNodeTreeZone *> zone_stack = tree_zones->get_zone_stack_for_node(
group_node->identifier);
for (const bNodeTreeZone *zone : zone_stack) {
compute_context_builder.push<bke::SimulationZoneComputeContext>(*zone->output_node);
switch (zone->output_node->type) {
case GEO_NODE_SIMULATION_OUTPUT: {
compute_context_builder.push<bke::SimulationZoneComputeContext>(*zone->output_node);
break;
}
case GEO_NODE_REPEAT_OUTPUT: {
/* Only show data from the first iteration for now. */
const int repeat_iteration = 0;
compute_context_builder.push<bke::RepeatZoneComputeContext>(*zone->output_node,
repeat_iteration);
break;
}
}
}
compute_context_builder.push<bke::NodeGroupComputeContext>(*group_node);
}
@ -638,6 +661,12 @@ const ViewerNodeLog *GeoModifierLog::find_viewer_node_log_for_path(const ViewerP
typed_elem.sim_output_node_id);
break;
}
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
const auto &typed_elem = *reinterpret_cast<const RepeatZoneViewerPathElem *>(elem);
compute_context_builder.push<bke::RepeatZoneComputeContext>(
typed_elem.repeat_output_node_id, typed_elem.iteration);
break;
}
default: {
BLI_assert_unreachable();
break;

@ -708,6 +708,7 @@ set(geo_node_tests
curves/interpolate_curves
geometry
instance
repeat_zone
mesh_primitives
mesh
mesh/extrude