Simulation nodes: UI for simulation state items in sidebar #106919

Merged
14 changed files with 821 additions and 62 deletions

View File

@ -5,6 +5,10 @@ from bpy.types import Operator
from bpy.app.translations import pgettext_data as data_
from bpy.props import (
EnumProperty,
)
def build_default_empty_geometry_node_group(name):
group = bpy.data.node_groups.new(name, 'GeometryNodeTree')
@ -237,8 +241,102 @@ class NewGeometryNodeTreeAssign(Operator):
return {'FINISHED'}
class SimulationZoneOperator():
input_node_type = 'GeometryNodeSimulationInput'
output_node_type = 'GeometryNodeSimulationOutput'
@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
@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 SimulationZoneItemAddOperator(SimulationZoneOperator, Operator):
'''Add a state item to the simulation zone'''
bl_idname = "node.simulation_zone_item_add"
bl_label = "Add State Item"
bl_options = {'REGISTER', 'UNDO'}
default_socket_type = 'GEOMETRY'
def execute(self, context):
node = self.get_output_node(context)
state_items = node.state_items
# Remember index to move the item.
dst_index = min(node.active_index + 1, len(state_items))
# Empty name so it is based on the type only.
state_items.new(self.default_socket_type, "")
state_items.move(len(state_items) - 1, dst_index)
node.active_index = dst_index
return {'FINISHED'}
class SimulationZoneItemRemoveOperator(SimulationZoneOperator, Operator):
'''Remove a state item from the simulation zone'''
bl_idname = "node.simulation_zone_item_remove"
bl_label = "Remove State Item"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
node = self.get_output_node(context)
state_items = node.state_items
if node.active_item:
state_items.remove(node.active_item)
node.active_index = min(node.active_index, len(state_items) - 1)
return {'FINISHED'}
class SimulationZoneItemMoveOperator(SimulationZoneOperator, Operator):
'''Move a simulation state item up or down in the list'''
bl_idname = "node.simulation_zone_item_move"
bl_label = "Move State 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)
state_items = node.state_items
if self.direction == 'UP' and node.active_index > 0:
state_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(state_items) - 1:
state_items.move(node.active_index, node.active_index + 1)
node.active_index = node.active_index + 1
return {'FINISHED'}
classes = (
NewGeometryNodesModifier,
NewGeometryNodeTreeAssign,
MoveModifierToNodes,
SimulationZoneItemAddOperator,
SimulationZoneItemRemoveOperator,
SimulationZoneItemMoveOperator,
)

View File

@ -954,6 +954,76 @@ class NODE_PT_node_tree_interface_outputs(NodeTreeInterfacePanel):
self.draw_socket_list(context, "OUT", "outputs", "active_output")
class NODE_UL_simulation_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_simulation_zone_items(Panel):
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = "Node"
bl_label = "Simulation State"
input_node_type = 'GeometryNodeSimulationInput'
output_node_type = 'GeometryNodeSimulationOutput'
@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
@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_simulation_zone_items", "", output_node, "state_items", output_node, "active_index")
ops_col = split.column()
add_remove_col = ops_col.column(align=True)
add_remove_col.operator("node.simulation_zone_item_add", icon='ADD', text="")
add_remove_col.operator("node.simulation_zone_item_remove", icon='REMOVE', text="")
ops_col.separator()
up_down_col = ops_col.column(align=True)
props = up_down_col.operator("node.simulation_zone_item_move", icon='TRIA_UP', text="")
props.direction = 'UP'
props = up_down_col.operator("node.simulation_zone_item_move", icon='TRIA_DOWN', text="")
props.direction = 'DOWN'
active_item = output_node.active_item
if active_item is not None:
layout.prop(active_item, "socket_type")
layout.prop(active_item, "name")
# Grease Pencil properties
class NODE_PT_annotation(AnnotationDataPanel, Panel):
bl_space_type = 'NODE_EDITOR'
@ -1018,6 +1088,8 @@ classes = (
NODE_UL_interface_sockets,
NODE_PT_node_tree_interface_inputs,
NODE_PT_node_tree_interface_outputs,
NODE_UL_simulation_zone_items,
NODE_PT_simulation_zone_items,
node_panel(EEVEE_MATERIAL_PT_settings),
node_panel(MATERIAL_PT_viewport),

View File

@ -572,6 +572,15 @@ class NodeTreeMainUpdater {
return true;
}
}
/* Check paired simulation zone nodes. */
LukasTonne marked this conversation as resolved
Review

Connecting the __extend__ socket updates both input and output node, but only because of the blanket NTREE_CHANGED_LINK handling which updates all nodes in the tree when a link is changed.

Property changes of the output node don't update the paired input node on their own, so i had to add this special case check. Any better suggestions would be welcome.

Connecting the `__extend__` socket updates both input and output node, but only because of the blanket `NTREE_CHANGED_LINK` handling which updates _all nodes in the tree_ when a link is changed. Property changes of the output node don't update the paired input node on their own, so i had to add this special case check. Any better suggestions would be welcome.
if (node.type == GEO_NODE_SIMULATION_INPUT) {
const NodeGeometrySimulationInput *data = static_cast<const NodeGeometrySimulationInput *>(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;
}

View File

@ -4308,6 +4308,70 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
/* Rename Grease Pencil weight draw brush. */
do_versions_rename_id(bmain, ID_BR, "Draw Weight", "Weight Draw");
/* Identifier generation for simulation node sockets changed.
* Update identifiers so links are not removed during validation. */
if (!DNA_struct_elem_find(fd->filesdna, "NodeSimulationItem", "int", "identifier")) {
static auto set_socket_identifiers = [](bNode *node, const bNode *output_node, int extra_outputs) {
const NodeGeometrySimulationOutput *output_data = static_cast<const NodeGeometrySimulationOutput *>(output_node->storage);
/* Includes extension socket. */
BLI_assert(BLI_listbase_count(&node->inputs) == output_data->items_num + 1);
/* Includes extension socket. */
BLI_assert(BLI_listbase_count(&node->outputs) == output_data->items_num + 1 + extra_outputs);
int i;
LISTBASE_FOREACH_INDEX (bNodeSocket *, sock, &node->inputs, i) {
/* Skip extension socket. */
if (i >= output_data->items_num) {
break;
}
BLI_snprintf(sock->identifier, sizeof(sock->identifier), "Item_%d", output_data->items[i].identifier);
}
LISTBASE_FOREACH_INDEX (bNodeSocket *, sock, &node->outputs, i) {
const int item_i = i - extra_outputs;
/* Skip extra outputs. */
if (i < extra_outputs) {
continue;
}
/* Skip extension socket. */
if (item_i >= output_data->items_num) {
break;
}
BLI_snprintf(sock->identifier, sizeof(sock->identifier), "Item_%d", output_data->items[i - extra_outputs].identifier);
}
};
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
if (ntree->type == NTREE_GEOMETRY) {
/* Initialize item identifiers. */
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type == GEO_NODE_SIMULATION_OUTPUT) {
NodeGeometrySimulationOutput *data = static_cast<NodeGeometrySimulationOutput *>(node->storage);
for (int i = 0; i < data->items_num; ++i) {
data->items[i].identifier = i;
}
data->next_identifier = data->items_num;
}
}
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type == GEO_NODE_SIMULATION_INPUT) {
const NodeGeometrySimulationInput *input_data = static_cast<const NodeGeometrySimulationInput *>(node->storage);
LISTBASE_FOREACH (bNode *, output_node, &ntree->nodes) {
if (output_node->identifier == input_data->output_node_id) {
/* 'Delta Time' socket in input nodes */
set_socket_identifiers(node, output_node, 1);
break;
}
}
}
if (node->type == GEO_NODE_SIMULATION_OUTPUT) {
set_socket_identifiers(node, node, 0);
}
}
}
}
}
}
/**

View File

@ -73,6 +73,7 @@ void ED_init_node_socket_type_virtual(struct bNodeSocketType *stype);
void ED_node_sample_set(const float col[4]);
void ED_node_draw_snap(
struct View2D *v2d, const float cent[2], float size, NodeBorder border, unsigned int pos);
void ED_node_type_draw_color(const char *idname, float *r_color);
/* node_draw.cc */

View File

@ -1497,6 +1497,23 @@ void ED_init_node_socket_type_virtual(bNodeSocketType *stype)
stype->draw_color = node_socket_virtual_draw_color;
}
void ED_node_type_draw_color(const char *idname, float *r_color)
{
using namespace blender::ed::space_node;
const bNodeSocketType *typeinfo = nodeSocketTypeFind(idname);
if (!typeinfo || typeinfo->type == SOCK_CUSTOM) {
r_color[0] = 0.0f;
r_color[1] = 0.0f;
r_color[2] = 0.0f;
r_color[3] = 0.0f;
return;
}
BLI_assert(typeinfo->type < ARRAY_SIZE(std_node_socket_colors));
copy_v4_v4(r_color, std_node_socket_colors[typeinfo->type]);
}
namespace blender::ed::space_node {
/* ************** Generic drawing ************** */

View File

@ -16,6 +16,8 @@
#ifdef __cplusplus
namespace blender {
template<typename T> class Span;
template<typename T> class MutableSpan;
class IndexRange;
class StringRef;
class StringRefNull;
} // namespace blender
@ -1600,7 +1602,9 @@ typedef struct NodeSimulationItem {
/* #eNodeSocketDatatype. */
/* TODO: Use a different enum instead to support Byte colors, etc. */
short socket_type;
char _pad[6];
short _pad;
/* Generates unique identifier for sockets. */
int identifier;
} NodeSimulationItem;
typedef struct NodeGeometrySimulationInput {
@ -1610,7 +1614,16 @@ typedef struct NodeGeometrySimulationInput {
typedef struct NodeGeometrySimulationOutput {
NodeSimulationItem *items;
int items_num;
char _pad[4];
int active_index;
/* Number to give unique IDs to state items. */
int next_identifier;
int _pad;
#ifdef __cplusplus
blender::Span<NodeSimulationItem> items_span() const;
blender::MutableSpan<NodeSimulationItem> items_span_for_write();
blender::IndexRange items_range() const;
#endif
} NodeGeometrySimulationOutput;
typedef struct NodeGeometryDistributePointsInVolume {

View File

@ -52,6 +52,8 @@
#include "RE_texture.h"
#include "NOD_composite.h"
#include "NOD_geometry.h"
#include "NOD_socket.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
@ -4087,6 +4089,60 @@ static void rna_NodeCryptomatte_update_remove(Main *bmain, Scene *scene, Pointer
rna_Node_update(bmain, scene, ptr);
}
static void rna_SimulationStateItem_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr)
{
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
NodeSimulationItem *item = (NodeSimulationItem *)ptr->data;
bNode *node = NOD_geometry_simulation_output_find_node_by_item(ntree, item);
BKE_ntree_update_tag_node_property(ntree, node);
ED_node_tree_propagate_change(NULL, bmain, ntree);
}
static bool rna_SimulationStateItem_socket_type_supported(const EnumPropertyItem *item)
{
return NOD_geometry_simulation_output_item_socket_type_supported((eNodeSocketDatatype)item->value);
}
static const EnumPropertyItem *rna_SimulationStateItem_socket_type_itemf(bContext *UNUSED(C),
PointerRNA *UNUSED(ptr),
PropertyRNA *UNUSED(prop),
bool *r_free)
{
*r_free = true;
return itemf_function_check(node_socket_data_type_items, rna_SimulationStateItem_socket_type_supported);
}
static void rna_SimulationStateItem_name_set(PointerRNA *ptr, const char *value)
{
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
NodeSimulationItem *item = (NodeSimulationItem *)ptr->data;
bNode *node = NOD_geometry_simulation_output_find_node_by_item(ntree, item);
NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage;
const char *defname = nodeStaticSocketLabel(item->socket_type, 0);
NOD_geometry_simulation_output_item_set_unique_name(sim, item, value, defname);
}
static void rna_SimulationStateItem_color_get(PointerRNA *ptr, float *values)
{
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
NodeSimulationItem *item = (NodeSimulationItem *)ptr->data;
const char *socket_type_idname = nodeStaticSocketType(item->socket_type, 0);
node_type_draw_color(socket_type_idname, values);
}
static PointerRNA rna_NodeGeometrySimulationInput_paired_output_get(PointerRNA *ptr)
{
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
bNode *node = (bNode *)ptr->data;
bNode *output_node = NOD_geometry_simulation_input_get_paired_output(ntree, node);
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)
{
@ -4108,6 +4164,100 @@ static bool rna_GeometryNodeSimulationInput_pair_with_output(
return true;
}
static NodeSimulationItem *rna_NodeGeometrySimulationOutput_items_new(
ID *id,
bNode *node,
Main *bmain,
ReportList *reports,
int socket_type,
const char *name)
{
NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage;
NodeSimulationItem *item = NOD_geometry_simulation_output_add_item(sim, (short)socket_type, name);
if (item == NULL) {
BKE_report(reports, RPT_ERROR, "Unable to create socket");
}
else {
bNodeTree *ntree = (bNodeTree *)id;
BKE_ntree_update_tag_node_property(ntree, node);
ED_node_tree_propagate_change(NULL, 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)
{
NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage;
if (!NOD_geometry_simulation_output_contains_item(sim, item)) {
BKE_reportf(reports, RPT_ERROR, "Unable to locate item '%s' in node", item->name);
}
else {
NOD_geometry_simulation_output_remove_item(sim, item);
bNodeTree *ntree = (bNodeTree *)id;
BKE_ntree_update_tag_node_property(ntree, node);
ED_node_tree_propagate_change(NULL, 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 = (NodeGeometrySimulationOutput *)node->storage;
NOD_geometry_simulation_output_clear_items(sim);
bNodeTree *ntree = (bNodeTree *)id;
BKE_ntree_update_tag_node_property(ntree, node);
ED_node_tree_propagate_change(NULL, bmain, ntree);
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
}
static void rna_NodeGeometrySimulationOutput_items_move(ID *id,
bNode *node,
Main *bmain,
int from_index,
int to_index)
{
NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage;
if (from_index < 0 || from_index >= sim->items_num || to_index < 0 || to_index >= sim->items_num) {
return;
}
NOD_geometry_simulation_output_move_item(sim, from_index, to_index);
bNodeTree *ntree = (bNodeTree *)id;
BKE_ntree_update_tag_node_property(ntree, node);
ED_node_tree_propagate_change(NULL, bmain, ntree);
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
}
static PointerRNA rna_NodeGeometrySimulationOutput_active_item_get(PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage;
NodeSimulationItem *item = NOD_geometry_simulation_output_get_active_item(sim);
PointerRNA r_ptr;
RNA_pointer_create(ptr->owner_id, &RNA_SimulationStateItem, item, &r_ptr);
return r_ptr;
}
static void rna_NodeGeometrySimulationOutput_active_item_set(PointerRNA *ptr, PointerRNA value)
{
bNode *node = (bNode *)ptr->data;
NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage;
NOD_geometry_simulation_output_set_active_item(sim, (NodeSimulationItem *)value.data);
}
/* ******** Node Socket Types ******** */
static PointerRNA rna_NodeOutputFile_slot_layer_get(CollectionPropertyIterator *iter)
@ -9759,9 +9909,19 @@ static void def_geo_set_curve_normal(StructRNA *srna)
static void def_geo_simulation_input(StructRNA *srna)
{
PropertyRNA *prop;
FunctionRNA *func;
PropertyRNA *parm;
RNA_def_struct_sdna_from(srna, "NodeGeometrySimulationInput", "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_NodeGeometrySimulationInput_paired_output_get", NULL, NULL, NULL);
RNA_def_property_ui_text(prop, "Paired Output", "Simulation output node that this input node is paired with");
func = RNA_def_function(
srna, "pair_with_output", "rna_GeometryNodeSimulationInput_pair_with_output");
RNA_def_function_ui_description(func, "Pair a simulation input node with an output node.");
@ -9777,16 +9937,71 @@ static void def_geo_simulation_input(StructRNA *srna)
static void rna_def_simulation_state_item(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "SimulationStateItem", NULL);
RNA_def_struct_ui_text(srna, "Simulation Sate Item", "");
RNA_def_struct_sdna(srna, "NodeSimulationItem");
PropertyRNA *prop;
StructRNA *srna = RNA_def_struct(brna, "SimulationStateItem", NULL);
RNA_def_struct_ui_text(srna, "Simulation Item", "");
RNA_def_struct_sdna(srna, "NodeSimulationItem");
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
RNA_def_property_string_funcs(prop, NULL, NULL, "rna_SimulationStateItem_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_NodeSocket_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_SimulationStateItem_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, NULL, NULL, "rna_SimulationStateItem_socket_type_itemf");
RNA_def_property_ui_text(prop, "Socket Type", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_SimulationStateItem_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_SimulationStateItem_color_get", NULL, NULL);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Color", "Socket color");
}
static void rna_def_geo_simulation_output_items(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *parm;
FunctionRNA *func;
srna = RNA_def_struct(brna, "NodeGeometrySimulationOutputItems", NULL);
RNA_def_struct_sdna(srna, "bNode");
RNA_def_struct_ui_text(srna, "Items", "Collection of simulation items");
func = RNA_def_function(srna, "new", "rna_NodeGeometrySimulationOutput_items_new");
RNA_def_function_ui_description(func, "Add a item to this simulation zone");

state item -> item for UI text. "State" is mostly redundant for users in this context.

`state item` -> `item` for UI text. "State" is mostly redundant for users in this context.
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, 0, PARM_REQUIRED);
parm = RNA_def_string(func, "name", NULL, MAX_NAME, "Name", "");
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
/* return value */
parm = RNA_def_pointer(func, "item", "SimulationStateItem", "Item", "New item");
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "remove", "rna_NodeGeometrySimulationOutput_items_remove");
RNA_def_function_ui_description(func, "Remove an item from this simulation zone");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
parm = RNA_def_pointer(func, "item", "SimulationStateItem", "Item", "The item to remove");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
func = RNA_def_function(srna, "clear", "rna_NodeGeometrySimulationOutput_items_clear");
RNA_def_function_ui_description(func, "Remove all items from this simulation zone");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
func = RNA_def_function(srna, "move", "rna_NodeGeometrySimulationOutput_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, 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, 0, PARM_REQUIRED);
}
static void def_geo_simulation_output(StructRNA *srna)
@ -9798,7 +10013,24 @@ static void def_geo_simulation_output(StructRNA *srna)
prop = RNA_def_property(srna, "state_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, NULL, "items", "items_num");
RNA_def_property_struct_type(prop, "SimulationStateItem");
RNA_def_property_ui_text(prop, "Inputs", "");
RNA_def_property_ui_text(prop, "Items", "");
RNA_def_property_srna(prop, "NodeGeometrySimulationOutputItems");
prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, NULL, "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, NULL);
prop = RNA_def_property(srna, "active_item", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "SimulationStateItem");
RNA_def_property_pointer_funcs(prop,
"rna_NodeGeometrySimulationOutput_active_item_get",
"rna_NodeGeometrySimulationOutput_active_item_set",
NULL,
NULL);
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
RNA_def_property_update(prop, NC_NODE, NULL);
}
static void def_geo_curve_handle_type_selection(StructRNA *srna)
@ -13222,6 +13454,7 @@ void RNA_def_nodetree(BlenderRNA *brna)
/* special socket types */
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_node_instance_hash(brna);
}

View File

@ -13,16 +13,70 @@ extern struct bNodeTreeType *ntreeType_Geometry;
void register_node_type_geo_custom_group(bNodeType *ntype);
/* -------------------------------------------------------------------- */
/** \name Simulation Node API
/** \name Simulation Input Node
* \{ */
struct bNode *NOD_geometry_simulation_input_get_paired_output(
struct bNodeTree *node_tree, const struct bNode *simulation_input_node);
/**
* Pair a simulation input node with an output node.
* \return True if pairing the node was successful.
*/
bool NOD_geometry_simulation_input_pair_with_output(const struct bNodeTree *node_tree,
struct bNode *sim_input_node,
const struct bNode *sim_output_node);
struct bNode *simulation_input_node,
const struct bNode *simulation_output_node);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Simulation Output Node
* \{ */
bool NOD_geometry_simulation_output_item_socket_type_supported(eNodeSocketDatatype socket_type);
/**
* Set a unique item name.

Blender uses syntax like \return rather than @return. Also for multi-line function docstring comments, the /** should be on its own line at the start.

Blender uses syntax like `\return` rather than `@return`. Also for multi-line function docstring comments, the `/**` should be on its own line at the start.
* \return True if the unique name differs from the original name.
*/
bool NOD_geometry_simulation_output_item_set_unique_name(struct NodeGeometrySimulationOutput *sim,
struct NodeSimulationItem *item,
const char *name,
const char *defname);
/**
* Find the node owning this simulation state item.
*/
bNode *NOD_geometry_simulation_output_find_node_by_item(struct bNodeTree *ntree,
const struct NodeSimulationItem *item);
bool NOD_geometry_simulation_output_contains_item(struct NodeGeometrySimulationOutput *sim,
const struct NodeSimulationItem *item);
struct NodeSimulationItem *NOD_geometry_simulation_output_get_active_item(
struct NodeGeometrySimulationOutput *sim);
void NOD_geometry_simulation_output_set_active_item(struct NodeGeometrySimulationOutput *sim,
struct NodeSimulationItem *item);
struct NodeSimulationItem *NOD_geometry_simulation_output_find_item(
struct NodeGeometrySimulationOutput *sim, const char *name);
struct NodeSimulationItem *NOD_geometry_simulation_output_add_item(
struct NodeGeometrySimulationOutput *sim, short socket_type, const char *name);
struct NodeSimulationItem *NOD_geometry_simulation_output_insert_item(
struct NodeGeometrySimulationOutput *sim, short socket_type, const char *name, int index);
struct NodeSimulationItem *NOD_geometry_simulation_output_add_item_from_socket(
struct NodeGeometrySimulationOutput *sim,
const struct bNode *from_node,
const struct bNodeSocket *from_sock);
struct NodeSimulationItem *NOD_geometry_simulation_output_insert_item_from_socket(
struct NodeGeometrySimulationOutput *sim,
const struct bNode *from_node,
const struct bNodeSocket *from_sock,
int index);
void NOD_geometry_simulation_output_remove_item(struct NodeGeometrySimulationOutput *sim,
struct NodeSimulationItem *item);
void NOD_geometry_simulation_output_clear_items(struct NodeGeometrySimulationOutput *sim);
void NOD_geometry_simulation_output_move_item(struct NodeGeometrySimulationOutput *sim,
int from_index,
int to_index);
/** \} */

View File

@ -22,6 +22,8 @@ struct bNodeTree;
extern "C" {
#endif
void node_type_draw_color(const char *idname, float *r_color);
struct bNodeSocket *node_add_socket_from_template(struct bNodeTree *ntree,
struct bNode *node,
struct bNodeSocketTemplate *stemp,

View File

@ -136,8 +136,5 @@ class FieldAtIndexInput final : public bke::GeometryFieldInput {
void socket_declarations_for_simulation_items(Span<NodeSimulationItem> items,
NodeDeclaration &r_declaration);
const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item);
/** \warning Return value will be reallocated when items are added or removed. */
NodeSimulationItem *simulation_item_add_from_socket(NodeGeometrySimulationOutput &storage,
const bNodeSocket &socket);
} // namespace blender::nodes

View File

@ -8,6 +8,7 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "NOD_geometry.h"
#include "NOD_socket.h"
#include "node_geometry_util.hh"
@ -141,8 +142,8 @@ static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
if (link->tonode == node) {
if (link->tosock->identifier == StringRef("__extend__")) {
if (const NodeSimulationItem *item = simulation_item_add_from_socket(storage,
*link->fromsock)) {
if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket(
&storage, link->fromnode, link->fromsock)) {
update_node_declaration_and_sockets(*ntree, *node);
link->tosock = nodeFindSocket(node, SOCK_IN, item->name);
}
@ -154,8 +155,8 @@ static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
else {
BLI_assert(link->fromnode == node);
if (link->fromsock->identifier == StringRef("__extend__")) {
if (const NodeSimulationItem *item = simulation_item_add_from_socket(storage,
*link->tosock)) {
if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket(
&storage, link->tonode, link->tosock)) {
update_node_declaration_and_sockets(*ntree, *node);
link->fromsock = nodeFindSocket(node, SOCK_OUT, item->name);
}
@ -185,6 +186,15 @@ void register_node_type_geo_simulation_input()
nodeRegisterType(&ntype);
}
bNode *NOD_geometry_simulation_input_get_paired_output(bNodeTree *node_tree,
const bNode *simulation_input_node)
{
namespace file_ns = blender::nodes::node_geo_simulation_input_cc;
const NodeGeometrySimulationInput &data = file_ns::node_storage(*simulation_input_node);
return node_tree->node_by_id(data.output_node_id);
}
bool NOD_geometry_simulation_input_pair_with_output(const bNodeTree *node_tree,
bNode *sim_input_node,
const bNode *sim_output_node)

View File

@ -8,7 +8,6 @@
#include "DEG_depsgraph_query.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "NOD_common.h"
#include "NOD_socket.h"
@ -25,15 +24,15 @@ void socket_declarations_for_simulation_items(const Span<NodeSimulationItem> ite
case SOCK_GEOMETRY: {
{
std::unique_ptr<decl::Geometry> decl = std::make_unique<decl::Geometry>();
decl->name = item.name;
decl->identifier = item.name;
decl->name = item.name ? item.name : "";
decl->identifier = "Item_" + std::to_string(item.identifier);

Assigning nullptr to a std::string is undefined behavior. We also ensure the item.name is never empty now, but it's better not to rely on that alone and check the char * as well.

Assigning nullptr to a `std::string` is undefined behavior. We also ensure the `item.name` is never empty now, but it's better not to rely on that alone and check the `char *` as well.
decl->in_out = SOCK_IN;
r_declaration.inputs.append(std::move(decl));
}
LukasTonne marked this conversation as resolved Outdated

How about decl->identifier = "Item_" + std::to_string(item.identifier);?

How about `decl->identifier = "Item_" + std::to_string(item.identifier);`?

Hah, i always forget how this number-to-string stuff works in C++, somehow find it easier to remember old BLI.

Hah, i always forget how this number-to-string stuff works in C++, somehow find it easier to remember old BLI.
{
std::unique_ptr<decl::Geometry> decl = std::make_unique<decl::Geometry>();
decl->name = item.name;
decl->identifier = item.name;
decl->name = item.name ? item.name : "";
decl->identifier = "Item_" + std::to_string(item.identifier);
decl->in_out = SOCK_OUT;
r_declaration.outputs.append(std::move(decl));
}
@ -47,13 +46,20 @@ void socket_declarations_for_simulation_items(const Span<NodeSimulationItem> ite
r_declaration.outputs.append(decl::create_extend_declaration(SOCK_OUT));
}
struct SimulationItemsUniqueNameArgs {
NodeGeometrySimulationOutput *sim;
const NodeSimulationItem *item;
};
static bool simulation_items_unique_name_check(void *arg, const char *name)
{
const NodeGeometrySimulationOutput &storage = *static_cast<const NodeGeometrySimulationOutput *>(
const SimulationItemsUniqueNameArgs &args = *static_cast<const SimulationItemsUniqueNameArgs *>(
arg);
for (const NodeSimulationItem &item : Span(storage.items, storage.items_num)) {
if (STREQ(item.name, name)) {
return true;
for (const NodeSimulationItem &item : args.sim->items_span()) {
if (&item != args.item) {
if (STREQ(item.name, name)) {

This function is now also used to check for uniqueness of items that are already in the list. This check avoids detecting duplicates from the same socket.

This function is now also used to check for uniqueness of items that are _already_ in the list. This check avoids detecting duplicates from the same socket.
return true;
}
HooglyBoogly marked this conversation as resolved
Review

This can be checked outside of the loop with Span::contains_ptr()

This can be checked outside of the loop with `Span::contains_ptr()`
Review

I'm not sure what you mean here.
I'm checking the iterator here to avoid comparing it to itself, to prevent fake name collision. Otherwise having an existing item "XY" and entering the name "XY" would turn it into "XY.001".

I'm not sure what you mean here. I'm checking the iterator here to avoid comparing it to itself, to prevent fake name collision. Otherwise having an existing item "XY" and entering the name "XY" would turn it into "XY.001".
Review

Also note i've added C++ spans to the NodeGeometrySimulationOutput now to make this a bit nicer.

Also note i've added C++ spans to the `NodeGeometrySimulationOutput` now to make this a bit nicer.
Review

Ah okay, didn't get the context fully here. Thanks.

Ah okay, didn't get the context fully here. Thanks.
}
}
if (STREQ(name, "Delta Time")) {
@ -62,37 +68,6 @@ static bool simulation_items_unique_name_check(void *arg, const char *name)
return false;
}
NodeSimulationItem *simulation_item_add_from_socket(NodeGeometrySimulationOutput &storage,
const bNodeSocket &socket)
{
if (socket.type != SOCK_GEOMETRY) {
return nullptr;
}
char unique_name[MAX_NAME + 4] = "";
BLI_uniquename_cb(simulation_items_unique_name_check,
&storage,
socket.name,
'.',
unique_name,
ARRAY_SIZE(unique_name));
NodeSimulationItem *old_items = storage.items;
storage.items = MEM_cnew_array<NodeSimulationItem>(storage.items_num + 1, __func__);
for (const int i : IndexRange(storage.items_num)) {
storage.items[i].name = old_items[i].name;
storage.items[i].socket_type = old_items[i].socket_type;
}
NodeSimulationItem &added_item = storage.items[storage.items_num];
added_item.name = BLI_strdup(unique_name);
added_item.socket_type = socket.type;
storage.items_num++;
MEM_SAFE_FREE(old_items);
return &added_item;
}
const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item)
{
switch (item.socket_type) {
@ -272,10 +247,15 @@ static void node_declare_dynamic(const bNodeTree & /*node_tree*/,
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometrySimulationOutput *data = MEM_cnew<NodeGeometrySimulationOutput>(__func__);
data->next_identifier = 0;
data->items = MEM_cnew_array<NodeSimulationItem>(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;
}
@ -299,8 +279,11 @@ static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const b
dst_storage->items = MEM_cnew_array<NodeSimulationItem>(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;
}
@ -314,8 +297,8 @@ static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
NodeGeometrySimulationOutput &storage = node_storage(*node);
if (link->tonode == node) {
if (link->tosock->identifier == StringRef("__extend__")) {
if (const NodeSimulationItem *item = simulation_item_add_from_socket(storage,
*link->fromsock)) {
if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket(
&storage, link->fromnode, link->fromsock)) {
update_node_declaration_and_sockets(*ntree, *node);
link->tosock = nodeFindSocket(node, SOCK_IN, item->name);
}
@ -327,8 +310,8 @@ static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
else {
BLI_assert(link->fromnode == node);
if (link->fromsock->identifier == StringRef("__extend__")) {
if (const NodeSimulationItem *item = simulation_item_add_from_socket(storage,
*link->tosock)) {
if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket(
&storage, link->fromnode, link->tosock)) {
update_node_declaration_and_sockets(*ntree, *node);
link->fromsock = nodeFindSocket(node, SOCK_OUT, item->name);
}
@ -359,3 +342,202 @@ void register_node_type_geo_simulation_output()
file_ns::node_copy_storage);
nodeRegisterType(&ntype);
}
blender::Span<NodeSimulationItem> NodeGeometrySimulationOutput::items_span() const
{
return blender::Span<NodeSimulationItem>(items, items_num);
}
blender::MutableSpan<NodeSimulationItem> NodeGeometrySimulationOutput::items_span_for_write()
{
return blender::MutableSpan<NodeSimulationItem>(items, items_num);
}
blender::IndexRange NodeGeometrySimulationOutput::items_range() const
{
return blender::IndexRange(items_num);
}
bool NOD_geometry_simulation_output_item_socket_type_supported(
const eNodeSocketDatatype socket_type)
{
return ELEM(socket_type, SOCK_GEOMETRY);
}
bNode *NOD_geometry_simulation_output_find_node_by_item(bNodeTree *ntree,
const NodeSimulationItem *item)
{
ntree->ensure_topology_cache();

Implement with Span::contains_ptr()

Implement with `Span::contains_ptr()`
for (bNode *node : ntree->nodes_by_type("GeometryNodeSimulationOutput")) {
NodeGeometrySimulationOutput *sim = static_cast<NodeGeometrySimulationOutput *>(node->storage);
if (sim->items_span().contains_ptr(item)) {
return node;
}
}
return nullptr;

A consistency suggestion: switch to return early in failure case, which is more common, and use index_range.contains():

if (!IndexRange(sim->items_num).contains(sim->active_index)) {
  return nullptr;
}
return &sim->items[sim->active_index];
A consistency suggestion: switch to return early in failure case, which is more common, and use `index_range.contains()`: ``` if (!IndexRange(sim->items_num).contains(sim->active_index)) { return nullptr; } return &sim->items[sim->active_index]; ```
}
bool NOD_geometry_simulation_output_item_set_unique_name(NodeGeometrySimulationOutput *sim,
NodeSimulationItem *item,
const char *name,
const char *defname)
{
char unique_name[MAX_NAME + 4];
BLI_strncpy(unique_name, name, sizeof(unique_name));
blender::nodes::SimulationItemsUniqueNameArgs args{sim, item};
const bool name_changed = BLI_uniquename_cb(blender::nodes::simulation_items_unique_name_check,
&args,
defname,
'.',
unique_name,
ARRAY_SIZE(unique_name));
item->name = BLI_strdup(unique_name);
for (NodeSimulationItem &item : blender::Span(sim->items, sim->items_num)) {
    if (STREQ(item.name, name)) {
      return &item;
    }
}
``` for (NodeSimulationItem &item : blender::Span(sim->items, sim->items_num)) { if (STREQ(item.name, name)) { return &item; } } ```
return name_changed;
}
bool NOD_geometry_simulation_output_contains_item(NodeGeometrySimulationOutput *sim,
const NodeSimulationItem *item)
{
return sim->items_span().contains_ptr(item);
}
NodeSimulationItem *NOD_geometry_simulation_output_get_active_item(
NodeGeometrySimulationOutput *sim)
{
if (!sim->items_range().contains(sim->active_index)) {
return nullptr;
}
return &sim->items[sim->active_index];
}
void NOD_geometry_simulation_output_set_active_item(NodeGeometrySimulationOutput *sim,
NodeSimulationItem *item)
{
if (sim->items_span().contains_ptr(item)) {

IndexRange(0, index) -> IndexRange(index)

`IndexRange(0, index)` -> `IndexRange(index)`
sim->active_index = item - sim->items;
}
}
NodeSimulationItem *NOD_geometry_simulation_output_find_item(NodeGeometrySimulationOutput *sim,
const char *name)
{
for (NodeSimulationItem &item : sim->items_span_for_write()) {
if (STREQ(item.name, name)) {
return &item;
}
}
return nullptr;
}
NodeSimulationItem *NOD_geometry_simulation_output_add_item(NodeGeometrySimulationOutput *sim,
const short socket_type,
const char *name)
{
return NOD_geometry_simulation_output_insert_item(sim, socket_type, name, sim->items_num);
}
NodeSimulationItem *NOD_geometry_simulation_output_insert_item(NodeGeometrySimulationOutput *sim,
const short socket_type,
const char *name,
int index)
{
NodeSimulationItem *old_items = sim->items;
sim->items = MEM_cnew_array<NodeSimulationItem>(sim->items_num + 1, __func__);
for (const int i : blender::IndexRange(index)) {
sim->items[i] = old_items[i];
}
for (const int i : blender::IndexRange(index, sim->items_num - index)) {
sim->items[i + 1] = old_items[i];
}
const char *defname = nodeStaticSocketLabel(socket_type, 0);
NodeSimulationItem &added_item = sim->items[index];
added_item.identifier = sim->next_identifier++;
NOD_geometry_simulation_output_item_set_unique_name(sim, &added_item, name, defname);
added_item.socket_type = socket_type;
sim->items_num++;
MEM_SAFE_FREE(old_items);
return &added_item;
}
NodeSimulationItem *NOD_geometry_simulation_output_add_item_from_socket(
NodeGeometrySimulationOutput *sim, const bNode * /*from_node*/, const bNodeSocket *from_sock)
{
if (from_sock->type != SOCK_GEOMETRY) {
return nullptr;
}
return NOD_geometry_simulation_output_insert_item(
sim, from_sock->type, from_sock->name, sim->items_num);
}
NodeSimulationItem *NOD_geometry_simulation_output_insert_item_from_socket(
NodeGeometrySimulationOutput *sim,
const bNode * /*from_node*/,
const bNodeSocket *from_sock,
int index)
{
return NOD_geometry_simulation_output_insert_item(sim, from_sock->type, from_sock->name, index);
}
void NOD_geometry_simulation_output_remove_item(NodeGeometrySimulationOutput *sim,
NodeSimulationItem *item)
{
const int index = item - sim->items;
if (index < 0 || index >= sim->items_num) {
return;
}
NodeSimulationItem *old_items = sim->items;
sim->items = MEM_cnew_array<NodeSimulationItem>(sim->items_num - 1, __func__);
for (const int i : blender::IndexRange(index)) {
sim->items[i] = old_items[i];
}
for (const int i : blender::IndexRange(index, sim->items_num - index).drop_front(1)) {
sim->items[i - 1] = old_items[i];
}
MEM_SAFE_FREE(old_items[index].name);
sim->items_num--;

else after return

else after return
MEM_SAFE_FREE(old_items);
}
void NOD_geometry_simulation_output_clear_items(struct NodeGeometrySimulationOutput *sim)
{
for (NodeSimulationItem &item : sim->items_span_for_write()) {
MEM_SAFE_FREE(item.name);
}
MEM_SAFE_FREE(sim->items);
sim->items = nullptr;
sim->items_num = 0;
}
void NOD_geometry_simulation_output_move_item(NodeGeometrySimulationOutput *sim,
int from_index,
int to_index)
{
BLI_assert(from_index >= 0 && from_index < sim->items_num);
BLI_assert(to_index >= 0 && to_index < sim->items_num);
if (from_index == to_index) {
return;
}
if (from_index < to_index) {
const NodeSimulationItem tmp = sim->items[from_index];
for (int i = from_index; i < to_index; ++i) {
sim->items[i] = sim->items[i + 1];
}
sim->items[to_index] = tmp;
}
else /* from_index > to_index */ {
const NodeSimulationItem tmp = sim->items[from_index];
for (int i = from_index; i > to_index; --i) {
sim->items[i] = sim->items[i - 1];
}
sim->items[to_index] = tmp;
}
}

View File

@ -39,6 +39,13 @@ using namespace blender;
using blender::fn::ValueOrField;
using blender::nodes::SocketDeclarationPtr;
extern "C" void ED_node_type_draw_color(const char *idname, float *r_color);
LukasTonne marked this conversation as resolved
Review

Exposing socket type colors through ED_node_type_draw_color adds another bad level call. Since the same file already has other UI functions used this way it doesn't make things any worse at least.

Exposing socket type colors through `ED_node_type_draw_color` adds another bad level call. Since the same file already has other UI functions used this way it doesn't make things any worse at least.
void node_type_draw_color(const char *idname, float *r_color)
{
ED_node_type_draw_color(idname, r_color);
}
struct bNodeSocket *node_add_socket_from_template(struct bNodeTree *ntree,
struct bNode *node,
struct bNodeSocketTemplate *stemp,