Geometry Nodes: new For Each Geometry Element zone

This adds a new type of zone to Geometry Nodes that allows executing some nodes
for each element in a geometry.

## Features

* The `Selection` input allows iterating over a subset of elements on the set
  domain.
* Fields passed into the input node are available as single values inside of the
  zone.
* The input geometry can be split up into separate (completely independent)
  geometries for each element (on all domains except face corner).
* New attributes can be created on the input geometry by outputting a single
  value from each iteration.
* New geometries can be generated in each iteration.
    * All of these geometries are joined to form the final output.
    * Attributes from the input geometry are propagated to the output
      geometries.

## Evaluation

The evaluation strategy is similar to the one used for repeat zones. Namely, it
dynamically builds a `lazy_function::Graph` once it knows how many iterations
are necessary. It contains a separate node for each iteration. The inputs for
each iteration are hardcoded into the graph. The outputs of each iteration a
passed to a separate lazy-function that reduces all the values down to the final
outputs. This final output can have a huge number of inputs and that is not
ideal for multi-threading yet, but that can still be improved in the future.

## Performance

There is a non-neglilible amount of overhead for each iteration. The overhead is
way larger than the per-element overhead when just doing field evaluation.
Therefore, normal field evaluation should be preferred when possible. That can
partially still be optimized if there is only some number crunching going on in
the zone but that optimization is not implemented yet.

However, processing many small geometries (e.g. each hair of a character
separately) will likely **always be slower** than working on fewer larger
geoemtries. The additional flexibility you get by processing each element
separately comes at the cost that Blender can't optimize the operation as well.
For node groups that need to handle lots of geometry elements, we recommend
trying to design the node setup so that iteration over tiny sub-geometries is
not required.

An opposite point is true as well though. It can be faster to process more
medium sized geometries in parallel than fewer very large geometries because of
more multi-threading opportunities. The exact threshold between tiny, medium and
large geometries depends on a lot of factors though.

Overall, this initial version of the new zone does not implement all
optimization opportunities yet, but the points mentioned above will still hold
true later.

Pull Request: https://projects.blender.org/blender/blender/pulls/127331
This commit is contained in:
Jacques Lucke
2024-09-24 11:52:02 +02:00
parent ebc4759df5
commit 6e5e01e630
42 changed files with 3607 additions and 99 deletions

View File

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

View File

@@ -173,6 +173,8 @@ class NodeAddZoneOperator(NodeAddOperator):
default=(150, 0),
)
add_default_geometry_link = True
def execute(self, context):
space = context.space_data
tree = space.edit_tree
@@ -189,11 +191,12 @@ class NodeAddZoneOperator(NodeAddOperator):
input_node.location -= Vector(self.offset)
output_node.location += Vector(self.offset)
# Connect geometry sockets by default.
# Get the sockets by their types, because the name is not guaranteed due to i18n.
from_socket = next(s for s in input_node.outputs if s.type == 'GEOMETRY')
to_socket = next(s for s in output_node.inputs if s.type == 'GEOMETRY')
tree.links.new(to_socket, from_socket)
if self.add_default_geometry_link:
# Connect geometry sockets by default if available.
# Get the sockets by their types, because the name is not guaranteed due to i18n.
from_socket = next(s for s in input_node.outputs if s.type == 'GEOMETRY')
to_socket = next(s for s in output_node.inputs if s.type == 'GEOMETRY')
tree.links.new(to_socket, from_socket)
return {'FINISHED'}
@@ -218,6 +221,17 @@ class NODE_OT_add_repeat_zone(NodeAddZoneOperator, Operator):
output_node_type = "GeometryNodeRepeatOutput"
class NODE_OT_add_foreach_geometry_element_zone(NodeAddZoneOperator, Operator):
"""Add a For Each Geometry Element zone that allows executing nodes e.g. for each vertex separately"""
bl_idname = "node.add_foreach_geometry_element_zone"
bl_label = "Add For Each Geometry Element Zone"
bl_options = {'REGISTER', 'UNDO'}
input_node_type = "GeometryNodeForeachGeometryElementInput"
output_node_type = "GeometryNodeForeachGeometryElementOutput"
add_default_geometry_link = False
class NODE_OT_collapse_hide_unused_toggle(Operator):
"""Toggle collapsed nodes and hide unused sockets"""
bl_idname = "node.collapse_hide_unused_toggle"
@@ -418,6 +432,7 @@ classes = (
NODE_OT_add_node,
NODE_OT_add_simulation_zone,
NODE_OT_add_repeat_zone,
NODE_OT_add_foreach_geometry_element_zone,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_interface_item_new,
NODE_OT_interface_item_duplicate,

View File

@@ -78,6 +78,13 @@ def add_repeat_zone(layout, label):
return props
def add_foreach_geometry_element_zone(layout, label):
props = layout.operator(
"node.add_foreach_geometry_element_zone", text=label, text_ctxt=i18n_contexts.default)
props.use_transform = True
return props
class NODE_MT_category_layout(Menu):
bl_idname = "NODE_MT_category_layout"
bl_label = "Layout"

View File

@@ -597,6 +597,7 @@ class NODE_MT_category_GEO_UTILITIES(Menu):
node_add_menu.add_node_type(layout, "GeometryNodeMenuSwitch")
node_add_menu.add_node_type(layout, "FunctionNodeRandomValue")
node_add_menu.add_repeat_zone(layout, label="Repeat Zone")
node_add_menu.add_foreach_geometry_element_zone(layout, label="For Each Geometry Element")
node_add_menu.add_node_type(layout, "GeometryNodeSwitch")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)

View File

@@ -31,7 +31,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 24
#define BLENDER_FILE_SUBVERSION 25
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@@ -123,6 +123,35 @@ class RepeatZoneComputeContext : public ComputeContext {
void print_current_in_line(std::ostream &stream) const override;
};
class ForeachGeometryElementZoneComputeContext : public ComputeContext {
private:
static constexpr const char *s_static_type = "FOREACH_GEOMETRY_ELEMENT_ZONE";
int32_t output_node_id_;
int index_;
public:
ForeachGeometryElementZoneComputeContext(const ComputeContext *parent,
int32_t output_node_id,
int index);
ForeachGeometryElementZoneComputeContext(const ComputeContext *parent,
const bNode &node,
int index);
int32_t output_node_id() const
{
return output_node_id_;
}
int index() const
{
return index_;
}
private:
void print_current_in_line(std::ostream &stream) const override;
};
class OperatorComputeContext : public ComputeContext {
private:
static constexpr const char *s_static_type = "OPERATOR";

View File

@@ -1376,6 +1376,8 @@ void node_tree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index);
#define GEO_NODE_GREASE_PENCIL_TO_CURVES 2145
#define GEO_NODE_IMPORT_PLY 2146
#define GEO_NODE_WARNING 2147
#define GEO_NODE_FOREACH_GEOMETRY_ELEMENT_INPUT 2148
#define GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT 2149
/** \} */

View File

@@ -33,7 +33,7 @@ class IDRemapper;
}
enum ViewerPathEqualFlag {
VIEWER_PATH_EQUAL_FLAG_IGNORE_REPEAT_ITERATION = (1 << 0),
VIEWER_PATH_EQUAL_FLAG_IGNORE_ITERATION = (1 << 0),
};
void BKE_viewer_path_init(ViewerPath *viewer_path);
@@ -55,6 +55,7 @@ GroupNodeViewerPathElem *BKE_viewer_path_elem_new_group_node();
SimulationZoneViewerPathElem *BKE_viewer_path_elem_new_simulation_zone();
ViewerNodeViewerPathElem *BKE_viewer_path_elem_new_viewer_node();
RepeatZoneViewerPathElem *BKE_viewer_path_elem_new_repeat_zone();
ForeachGeometryElementZoneViewerPathElem *BKE_viewer_path_elem_new_foreach_geometry_element_zone();
ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src);
bool BKE_viewer_path_elem_equal(const ViewerPathElem *a,
const ViewerPathElem *b,

View File

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

View File

@@ -85,6 +85,7 @@
#include "NOD_composite.hh"
#include "NOD_geo_bake.hh"
#include "NOD_geo_capture_attribute.hh"
#include "NOD_geo_foreach_geometry_element.hh"
#include "NOD_geo_index_switch.hh"
#include "NOD_geo_menu_switch.hh"
#include "NOD_geo_repeat.hh"
@@ -905,6 +906,11 @@ void node_tree_blend_write(BlendWriter *writer, bNodeTree *ntree)
if (node->type == GEO_NODE_MENU_SWITCH) {
nodes::MenuSwitchItemsAccessor::blend_write(writer, *node);
}
if (node->type == GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT) {
nodes::ForeachGeometryElementInputItemsAccessor::blend_write(writer, *node);
nodes::ForeachGeometryElementGenerationItemsAccessor::blend_write(writer, *node);
nodes::ForeachGeometryElementMainItemsAccessor::blend_write(writer, *node);
}
}
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
@@ -1176,6 +1182,12 @@ void node_tree_blend_read_data(BlendDataReader *reader, ID *owner_id, bNodeTree
nodes::RepeatItemsAccessor::blend_read_data(reader, *node);
break;
}
case GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT: {
nodes::ForeachGeometryElementInputItemsAccessor::blend_read_data(reader, *node);
nodes::ForeachGeometryElementMainItemsAccessor::blend_read_data(reader, *node);
nodes::ForeachGeometryElementGenerationItemsAccessor::blend_read_data(reader, *node);
break;
}
case GEO_NODE_INDEX_SWITCH: {
nodes::IndexSwitchItemsAccessor::blend_read_data(reader, *node);
break;

View File

@@ -407,6 +407,29 @@ static AnonymousAttributeInferencingResult analyze_anonymous_attribute_usages(
available_fields_by_geometry_socket[dst_index] |=
available_fields_by_geometry_socket[src_index];
}
/* This zone needs additional special handling because attributes from the input geometry
* are propagated to the output node. */
if (node->type == GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT) {
if (zones == nullptr) {
break;
}
const bNodeTreeZone *zone = zones->get_zone_by_node(node->identifier);
if (!zone->input_node) {
break;
}
const bNode *input_node = zone->input_node;
const bNode *output_node = node;
const int src_index = input_node->input_socket(0).index_in_tree();
for (const bNodeSocket *output_socket : output_node->output_sockets()) {
if (output_socket->type == SOCK_GEOMETRY) {
const int dst_index = output_socket->index_in_tree();
propagated_geometries_by_socket[dst_index] |=
propagated_geometries_by_socket[src_index];
available_fields_by_geometry_socket[dst_index] |=
available_fields_by_geometry_socket[src_index];
}
}
}
break;
}
/* The repeat output node needs special handling for two reasons:
@@ -637,6 +660,32 @@ static AnonymousAttributeInferencingResult analyze_anonymous_attribute_usages(
required_fields_by_geometry_socket[geometry_socket.index_in_tree()] |=
propagated_fields_by_socket[field_socket.index_in_tree()];
}
switch (node->type) {
case GEO_NODE_FOREACH_GEOMETRY_ELEMENT_INPUT: {
if (!zones) {
break;
}
/* Propagate from the geometry outputs to the geometry input. */
const bNodeTreeZone *zone = zones->get_zone_by_node(node->identifier);
if (!zone) {
break;
}
const bNode *input_node = node;
const bNode *output_node = zone->output_node;
const int dst_index = input_node->input_socket(0).index_in_tree();
for (const bNodeSocket *output_socket : output_node->output_sockets()) {
if (output_socket->type == SOCK_GEOMETRY) {
const int src_index = output_socket->index_in_tree();
required_fields_by_geometry_socket[dst_index] |=
required_fields_by_geometry_socket[src_index];
propagate_to_output_by_geometry_socket[dst_index] |=
propagate_to_output_by_geometry_socket[src_index];
}
}
break;
}
}
}
};

View File

@@ -1489,6 +1489,32 @@ class NodeTreeMainUpdater {
}
}
}
/* Zones may propagate changes from the input node to the output node even though there is
* no explicit link. */
switch (node.type) {
case GEO_NODE_REPEAT_OUTPUT:
case GEO_NODE_SIMULATION_OUTPUT:
case GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT: {
const bNodeTreeZones *zones = tree.zones();
if (!zones) {
break;
}
const bNodeTreeZone *zone = zones->get_zone_by_node(node.identifier);
if (!zone->input_node) {
break;
}
for (const bNodeSocket *input_socket : zone->input_node->input_sockets()) {
if (input_socket->is_available()) {
bool &pushed = pushed_by_socket_id[input_socket->index_in_tree()];
if (!pushed) {
sockets_to_check.push(input_socket);
pushed = true;
}
}
}
break;
}
}
/* The Normal node has a special case, because the value stored in the first output
* socket is used as input in the node. */
if (node.type == SH_NODE_NORMAL && socket.index() == 1) {

View File

@@ -95,6 +95,12 @@ void BKE_viewer_path_blend_write(BlendWriter *writer, const ViewerPath *viewer_p
BLO_write_struct(writer, RepeatZoneViewerPathElem, typed_elem);
break;
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE: {
const auto *typed_elem = reinterpret_cast<ForeachGeometryElementZoneViewerPathElem *>(
elem);
BLO_write_struct(writer, ForeachGeometryElementZoneViewerPathElem, typed_elem);
break;
}
}
BLO_write_string(writer, elem->ui_name);
}
@@ -110,6 +116,7 @@ void BKE_viewer_path_blend_read_data(BlendDataReader *reader, ViewerPath *viewer
case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE:
case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE:
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE:
case VIEWER_PATH_ELEM_TYPE_ID: {
break;
}
@@ -135,7 +142,8 @@ void BKE_viewer_path_foreach_id(LibraryForeachIDData *data, ViewerPath *viewer_p
case VIEWER_PATH_ELEM_TYPE_GROUP_NODE:
case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE:
case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE:
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE: {
break;
}
}
@@ -156,7 +164,8 @@ void BKE_viewer_path_id_remap(ViewerPath *viewer_path,
case VIEWER_PATH_ELEM_TYPE_GROUP_NODE:
case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE:
case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE:
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE: {
break;
}
}
@@ -191,6 +200,9 @@ ViewerPathElem *BKE_viewer_path_elem_new(const ViewerPathElemType type)
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
return &make_elem<RepeatZoneViewerPathElem>(type)->base;
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE: {
return &make_elem<ForeachGeometryElementZoneViewerPathElem>(type)->base;
}
}
BLI_assert_unreachable();
return nullptr;
@@ -231,6 +243,12 @@ RepeatZoneViewerPathElem *BKE_viewer_path_elem_new_repeat_zone()
BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE));
}
ForeachGeometryElementZoneViewerPathElem *BKE_viewer_path_elem_new_foreach_geometry_element_zone()
{
return reinterpret_cast<ForeachGeometryElementZoneViewerPathElem *>(
BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE));
}
ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src)
{
ViewerPathElem *dst = BKE_viewer_path_elem_new(ViewerPathElemType(src->type));
@@ -277,6 +295,14 @@ ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src)
new_elem->iteration = old_elem->iteration;
break;
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE: {
const auto *old_elem = reinterpret_cast<const ForeachGeometryElementZoneViewerPathElem *>(
src);
auto *new_elem = reinterpret_cast<ForeachGeometryElementZoneViewerPathElem *>(dst);
new_elem->zone_output_node_id = old_elem->zone_output_node_id;
new_elem->index = old_elem->index;
break;
}
}
return dst;
}
@@ -318,9 +344,16 @@ bool BKE_viewer_path_elem_equal(const ViewerPathElem *a,
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 &&
((flag & VIEWER_PATH_EQUAL_FLAG_IGNORE_REPEAT_ITERATION) != 0 ||
((flag & VIEWER_PATH_EQUAL_FLAG_IGNORE_ITERATION) != 0 ||
a_elem->iteration == b_elem->iteration);
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE: {
const auto *a_elem = reinterpret_cast<const ForeachGeometryElementZoneViewerPathElem *>(a);
const auto *b_elem = reinterpret_cast<const ForeachGeometryElementZoneViewerPathElem *>(b);
return a_elem->zone_output_node_id == b_elem->zone_output_node_id &&
((flag & VIEWER_PATH_EQUAL_FLAG_IGNORE_ITERATION) != 0 ||
a_elem->index == b_elem->index);
}
}
return false;
}
@@ -332,7 +365,8 @@ void BKE_viewer_path_elem_free(ViewerPathElem *elem)
case VIEWER_PATH_ELEM_TYPE_GROUP_NODE:
case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE:
case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE:
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE:
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE: {
break;
}
case VIEWER_PATH_ELEM_TYPE_MODIFIER: {

View File

@@ -194,6 +194,10 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme)
FROM_DEFAULT_V4_UCHAR(tui.icon_autokey);
}
if (!USER_VERSION_ATLEAST(403, 25)) {
FROM_DEFAULT_V4_UCHAR(space_node.node_zone_foreach_geometry_element);
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a USER_VERSION_ATLEAST check.

View File

@@ -196,6 +196,7 @@ enum ThemeColorID {
TH_NODE_ZONE_SIMULATION,
TH_NODE_ZONE_REPEAT,
TH_NODE_ZONE_FOREACH_GEOMETRY_ELEMENT,
TH_SIMULATED_FRAMES,
TH_CONSOLE_OUTPUT,

View File

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

View File

@@ -945,6 +945,15 @@ static void node_update_basis_from_declaration(
/* Start by adding root panel items. */
LocationUpdateState location_state(item_data);
/* Draw buttons at the top when the node has a custom socket order. This could be customized in
* the future to support showing the buttons in any place. */
if (node.declaration()->allow_any_socket_order) {
location_state.buttons_drawn = true;
location_state.need_spacer_after_item = node_update_basis_buttons(
C, ntree, node, node.typeinfo->draw_buttons, block, locy);
}
add_panel_items_recursive(
C, ntree, node, block, locx, locy, -1, false, "", nullptr, location_state);
@@ -2767,6 +2776,9 @@ static std::optional<std::chrono::nanoseconds> geo_node_get_execution_time(
return nullptr;
}
const bNodeTreeZone *zone = zones->get_zone_by_node(node.identifier);
if (zone && ELEM(&node, zone->input_node, zone->output_node)) {
zone = zone->parent_zone;
}
return tree_draw_ctx.geo_log_by_zone.lookup_default(zone, nullptr);
}();
@@ -3136,7 +3148,12 @@ static Vector<NodeExtraInfoRow> node_get_extra_info(const bContext &C,
if (snode.overlay.flag & SN_OVERLAY_SHOW_TIMINGS &&
(ELEM(node.typeinfo->nclass, NODE_CLASS_GEOMETRY, NODE_CLASS_GROUP, NODE_CLASS_ATTRIBUTE) ||
ELEM(node.type, NODE_FRAME, NODE_GROUP_OUTPUT)))
ELEM(node.type,
NODE_FRAME,
NODE_GROUP_OUTPUT,
GEO_NODE_SIMULATION_OUTPUT,
GEO_NODE_REPEAT_OUTPUT,
GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT)))
{
std::optional<NodeExtraInfoRow> row = node_get_execution_time_label_row(
tree_draw_ctx, snode, node);

View File

@@ -373,6 +373,13 @@ bool push_compute_context_for_tree_path(const SpaceNode &snode,
storage.inspection_index);
break;
}
case GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT: {
const auto &storage = *static_cast<const NodeGeometryForeachGeometryElementOutput *>(
zone->output_node->storage);
compute_context_builder.push<bke::ForeachGeometryElementZoneComputeContext>(
*zone->output_node, storage.inspection_index);
break;
}
}
}
compute_context_builder.push<bke::GroupNodeComputeContext>(*group_node, *tree);

View File

@@ -44,6 +44,15 @@ static ViewerPathElem *viewer_path_elem_for_zone(const bNodeTreeZone &zone)
node_elem->iteration = storage.inspection_index;
return &node_elem->base;
}
case GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT: {
const auto &storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(
zone.output_node->storage);
ForeachGeometryElementZoneViewerPathElem *node_elem =
BKE_viewer_path_elem_new_foreach_geometry_element_zone();
node_elem->zone_output_node_id = zone.output_node->identifier;
node_elem->index = storage.inspection_index;
return &node_elem->base;
}
}
BLI_assert_unreachable();
return nullptr;
@@ -256,7 +265,8 @@ std::optional<ViewerPathForGeometryNodesViewer> parse_geometry_nodes_viewer(
if (!ELEM(elem->type,
VIEWER_PATH_ELEM_TYPE_GROUP_NODE,
VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE,
VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE))
VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE,
VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE))
{
return std::nullopt;
}
@@ -323,6 +333,20 @@ bool exists_geometry_nodes_viewer(const ViewerPathForGeometryNodesViewer &parsed
zone = next_zone;
break;
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE: {
const auto &typed_elem =
*reinterpret_cast<const ForeachGeometryElementZoneViewerPathElem *>(path_elem);
const bNodeTreeZone *next_zone = tree_zones->get_zone_by_node(
typed_elem.zone_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);
@@ -413,7 +437,7 @@ UpdateActiveGeometryNodesViewerResult update_active_geometry_nodes_viewer(const
BLI_SCOPED_DEFER([&]() { BKE_viewer_path_clear(&tmp_viewer_path); });
viewer_path_for_geometry_node(snode, *viewer_node, tmp_viewer_path);
if (!BKE_viewer_path_equal(
&viewer_path, &tmp_viewer_path, VIEWER_PATH_EQUAL_FLAG_IGNORE_REPEAT_ITERATION))
&viewer_path, &tmp_viewer_path, VIEWER_PATH_EQUAL_FLAG_IGNORE_ITERATION))
{
continue;
}
@@ -487,6 +511,13 @@ bNode *find_geometry_nodes_viewer(const ViewerPath &viewer_path, SpaceNode &snod
elem.iteration);
return true;
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE: {
const auto &elem = reinterpret_cast<const ForeachGeometryElementZoneViewerPathElem &>(
elem_generic);
compute_context_builder.push<bke::ForeachGeometryElementZoneComputeContext>(
elem.zone_output_node_id, elem.index);
return true;
}
}
return false;
}

View File

@@ -458,7 +458,7 @@ class FieldEvaluator : NonMovable, NonCopyable {
return this->get_evaluated(field_index).typed<T>();
}
IndexMask get_evaluated_selection_as_mask();
IndexMask get_evaluated_selection_as_mask() const;
/**
* Retrieve the output of an evaluated boolean field and convert it to a mask, which can be used

View File

@@ -819,7 +819,7 @@ IndexMask FieldEvaluator::get_evaluated_as_mask(const int field_index)
return index_mask_from_selection(mask_, varray, scope_);
}
IndexMask FieldEvaluator::get_evaluated_selection_as_mask()
IndexMask FieldEvaluator::get_evaluated_selection_as_mask() const
{
BLI_assert(is_evaluated_);
return selection_mask_;

View File

@@ -19,6 +19,7 @@ set(SRC
intern/add_curves_on_mesh.cc
intern/curve_constraints.cc
intern/extend_curves.cc
intern/extract_elements.cc
intern/fillet_curves.cc
intern/interpolate_curves.cc
intern/join_geometries.cc
@@ -57,6 +58,7 @@ set(SRC
GEO_add_curves_on_mesh.hh
GEO_curve_constraints.hh
GEO_extend_curves.hh
GEO_extract_elements.hh
GEO_fillet_curves.hh
GEO_interpolate_curves.hh
GEO_join_geometries.hh

View File

@@ -0,0 +1,67 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BKE_attribute_filter.hh"
#include "BLI_array.hh"
#include "BLI_index_mask_fwd.hh"
struct Mesh;
struct PointCloud;
struct Curves;
struct GreasePencil;
namespace blender::bke {
class Instances;
}
namespace blender::geometry {
Array<Mesh *> extract_mesh_vertices(const Mesh &mesh,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter);
Array<Mesh *> extract_mesh_edges(const Mesh &mesh,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter);
Array<Mesh *> extract_mesh_faces(const Mesh &mesh,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter);
Array<PointCloud *> extract_pointcloud_points(const PointCloud &pointcloud,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter);
Array<Curves *> extract_curves_points(const Curves &curves,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter);
Array<Curves *> extract_curves(const Curves &curves,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter);
Array<bke::Instances *> extract_instances(const bke::Instances &instances,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter);
Array<GreasePencil *> extract_greasepencil_layers(const GreasePencil &grease_pencil,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter);
Array<GreasePencil *> extract_greasepencil_layer_points(
const GreasePencil &grease_pencil,
int layer_i,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter);
Array<GreasePencil *> extract_greasepencil_layer_curves(
const GreasePencil &grease_pencil,
int layer_i,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter);
} // namespace blender::geometry

View File

@@ -0,0 +1,543 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GEO_extract_elements.hh"
#include "BLI_index_mask.hh"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_mesh.hh"
#include "BKE_pointcloud.hh"
namespace blender::geometry {
using bke::AttrDomain;
struct PropagationAttribute {
StringRef name;
eCustomDataType cd_type;
AttrDomain domain;
GVArray data;
};
Array<Mesh *> extract_mesh_vertices(const Mesh &mesh,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= mesh.verts_num);
Array<Mesh *> elements(mask.size(), nullptr);
const bke::AttributeAccessor src_attributes = mesh.attributes();
Vector<PropagationAttribute> propagation_attributes;
src_attributes.for_all(
[&](const StringRef attribute_name, const bke::AttributeMetaData meta_data) {
if (meta_data.data_type == CD_PROP_STRING) {
return true;
}
if (attribute_filter.allow_skip(attribute_name)) {
return true;
}
const bke::GAttributeReader src_attribute = src_attributes.lookup(attribute_name,
AttrDomain::Point);
if (!src_attribute) {
return true;
}
propagation_attributes.append(
{attribute_name, meta_data.data_type, AttrDomain::Point, *src_attribute});
return true;
});
mask.foreach_index(GrainSize(32), [&](const int vert_i, const int element_i) {
Mesh *element = BKE_mesh_new_nomain(1, 0, 0, 0);
BKE_mesh_copy_parameters_for_eval(element, &mesh);
bke::MutableAttributeAccessor element_attributes = element->attributes_for_write();
for (const PropagationAttribute &src_attribute : propagation_attributes) {
bke::GSpanAttributeWriter dst = element_attributes.lookup_or_add_for_write_only_span(
src_attribute.name, AttrDomain::Point, src_attribute.cd_type);
if (!dst) {
continue;
}
src_attribute.data.get(vert_i, dst.span[0]);
dst.finish();
}
elements[element_i] = element;
});
return elements;
}
Array<Mesh *> extract_mesh_edges(const Mesh &mesh,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= mesh.edges_num);
Array<Mesh *> elements(mask.size(), nullptr);
const Span<int2> src_edges = mesh.edges();
const bke::AttributeAccessor src_attributes = mesh.attributes();
Vector<PropagationAttribute> propagation_attributes;
src_attributes.for_all(
[&](const StringRef attribute_name, const bke::AttributeMetaData meta_data) {
if (meta_data.data_type == CD_PROP_STRING) {
return true;
}
if (attribute_name == ".edge_verts") {
return true;
}
if (attribute_filter.allow_skip(attribute_name)) {
return true;
}
const bke::GAttributeReader src_attribute = src_attributes.lookup(attribute_name);
if (!src_attribute) {
return true;
}
if (ELEM(src_attribute.domain, AttrDomain::Point, AttrDomain::Edge)) {
propagation_attributes.append(
{attribute_name, meta_data.data_type, src_attribute.domain, *src_attribute});
}
else if (src_attribute.domain == AttrDomain::Corner) {
if (GVArray adapted_attribute = src_attributes.adapt_domain(
*src_attribute, src_attribute.domain, AttrDomain::Point))
{
propagation_attributes.append(
{attribute_name, meta_data.data_type, AttrDomain::Point, adapted_attribute});
}
}
else if (src_attribute.domain == AttrDomain::Face) {
if (GVArray adapted_attribute = src_attributes.adapt_domain(
*src_attribute, src_attribute.domain, AttrDomain::Edge))
{
propagation_attributes.append(
{attribute_name, meta_data.data_type, AttrDomain::Edge, adapted_attribute});
}
}
return true;
});
mask.foreach_index(GrainSize(32), [&](const int edge_i, const int element_i) {
Mesh *element = BKE_mesh_new_nomain(2, 1, 0, 0);
BKE_mesh_copy_parameters_for_eval(element, &mesh);
MutableSpan<int2> element_edges = element->edges_for_write();
element_edges[0] = {0, 1};
const int2 &src_edge = src_edges[edge_i];
bke::MutableAttributeAccessor element_attributes = element->attributes_for_write();
for (const PropagationAttribute &src_attribute : propagation_attributes) {
bke::GSpanAttributeWriter dst = element_attributes.lookup_or_add_for_write_only_span(
src_attribute.name, src_attribute.domain, src_attribute.cd_type);
if (!dst) {
continue;
}
if (src_attribute.domain == AttrDomain::Point) {
src_attribute.data.get(src_edge[0], dst.span[0]);
src_attribute.data.get(src_edge[1], dst.span[1]);
}
else {
src_attribute.data.get(edge_i, dst.span[0]);
}
dst.finish();
}
elements[element_i] = element;
});
return elements;
}
Array<Mesh *> extract_mesh_faces(const Mesh &mesh,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= mesh.faces_num);
Array<Mesh *> elements(mask.size(), nullptr);
const Span<int> src_corner_verts = mesh.corner_verts();
const Span<int> src_corner_edges = mesh.corner_edges();
const OffsetIndices<int> src_faces = mesh.faces();
const bke::AttributeAccessor src_attributes = mesh.attributes();
Vector<PropagationAttribute> propagation_attributes;
src_attributes.for_all(
[&](const StringRef attribute_name, const bke::AttributeMetaData meta_data) {
if (meta_data.data_type == CD_PROP_STRING) {
return true;
}
if (ELEM(attribute_name, ".edge_verts", ".corner_edge", ".corner_vert")) {
return true;
}
if (attribute_filter.allow_skip(attribute_name)) {
return true;
}
const bke::GAttributeReader src_attribute = src_attributes.lookup(attribute_name);
if (!src_attribute) {
return true;
}
propagation_attributes.append(
{attribute_name, meta_data.data_type, src_attribute.domain, *src_attribute});
return true;
});
mask.foreach_index(GrainSize(32), [&](const int face_i, const int element_i) {
const IndexRange src_face = src_faces[face_i];
const int verts_num = src_face.size();
Mesh *element = BKE_mesh_new_nomain(verts_num, verts_num, 1, verts_num);
BKE_mesh_copy_parameters_for_eval(element, &mesh);
MutableSpan<int2> element_edges = element->edges_for_write();
MutableSpan<int> element_corner_verts = element->corner_verts_for_write();
MutableSpan<int> element_corner_edges = element->corner_edges_for_write();
MutableSpan<int> element_face_offsets = element->face_offsets_for_write();
for (const int i : IndexRange(verts_num)) {
element_edges[i] = {i, i + 1};
element_corner_verts[i] = i;
element_corner_edges[i] = i;
}
element_edges.last()[1] = 0;
element_face_offsets[0] = 0;
element_face_offsets[1] = verts_num;
bke::MutableAttributeAccessor element_attributes = element->attributes_for_write();
for (const PropagationAttribute &src_attribute : propagation_attributes) {
bke::GSpanAttributeWriter dst = element_attributes.lookup_or_add_for_write_only_span(
src_attribute.name, src_attribute.domain, src_attribute.cd_type);
if (!dst) {
continue;
}
switch (src_attribute.domain) {
case AttrDomain::Point: {
for (const int i : IndexRange(verts_num)) {
const int src_corner_i = src_face[i];
const int src_vert_i = src_corner_verts[src_corner_i];
src_attribute.data.get(src_vert_i, dst.span[i]);
}
break;
}
case AttrDomain::Edge: {
for (const int i : IndexRange(verts_num)) {
const int src_corner_i = src_face[i];
const int src_edge_i = src_corner_edges[src_corner_i];
src_attribute.data.get(src_edge_i, dst.span[i]);
}
break;
}
case AttrDomain::Corner: {
src_attribute.data.materialize_compressed(src_face, dst.span.data());
break;
}
case AttrDomain::Face: {
src_attribute.data.get(face_i, dst.span[0]);
break;
}
default:
BLI_assert_unreachable();
break;
}
dst.finish();
}
elements[element_i] = element;
});
return elements;
}
Array<PointCloud *> extract_pointcloud_points(const PointCloud &pointcloud,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= pointcloud.totpoint);
Array<PointCloud *> elements(mask.size(), nullptr);
const bke::AttributeAccessor src_attributes = pointcloud.attributes();
mask.foreach_index(GrainSize(32), [&](const int point_i, const int element_i) {
PointCloud *element = BKE_pointcloud_new_nomain(1);
element->totcol = pointcloud.totcol;
element->mat = static_cast<Material **>(MEM_dupallocN(pointcloud.mat));
bke::gather_attributes(src_attributes,
AttrDomain::Point,
AttrDomain::Point,
attribute_filter,
Span<int>{point_i},
element->attributes_for_write());
elements[element_i] = element;
});
return elements;
}
Array<Curves *> extract_curves_points(const Curves &curves,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= curves.geometry.point_num);
Array<Curves *> elements(mask.size(), nullptr);
const bke::CurvesGeometry &src_curves = curves.geometry.wrap();
const bke::AttributeAccessor src_attributes = src_curves.attributes();
const Array<int> point_to_curve_map = src_curves.point_to_curve_map();
mask.foreach_index(GrainSize(32), [&](const int point_i, const int element_i) {
const int curve_i = point_to_curve_map[point_i];
/* Actual curve type is propagated below. */
Curves *element = bke::curves_new_nomain_single(1, CURVE_TYPE_POLY);
bke::curves_copy_parameters(curves, *element);
bke::MutableAttributeAccessor element_attributes =
element->geometry.wrap().attributes_for_write();
bke::gather_attributes(src_attributes,
AttrDomain::Point,
AttrDomain::Point,
attribute_filter,
Span<int>{point_i},
element_attributes);
bke::gather_attributes(src_attributes,
AttrDomain::Curve,
AttrDomain::Curve,
attribute_filter,
Span<int>{curve_i},
element_attributes);
elements[element_i] = element;
});
return elements;
}
Array<Curves *> extract_curves(const Curves &curves,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= curves.geometry.curve_num);
Array<Curves *> elements(mask.size(), nullptr);
const bke::CurvesGeometry &src_curves = curves.geometry.wrap();
const bke::AttributeAccessor src_attributes = src_curves.attributes();
const OffsetIndices<int> src_points_by_curve = src_curves.points_by_curve();
mask.foreach_index(GrainSize(32), [&](const int curve_i, const int element_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const int points_num = src_points.size();
Curves *element = bke::curves_new_nomain_single(points_num, CURVE_TYPE_POLY);
bke::MutableAttributeAccessor element_attributes =
element->geometry.wrap().attributes_for_write();
bke::curves_copy_parameters(curves, *element);
bke::gather_attributes(src_attributes,
AttrDomain::Point,
AttrDomain::Point,
attribute_filter,
src_points,
element_attributes);
bke::gather_attributes(src_attributes,
AttrDomain::Curve,
AttrDomain::Curve,
attribute_filter,
Span<int>{curve_i},
element_attributes);
element->geometry.wrap().update_curve_types();
elements[element_i] = element;
});
return elements;
}
Array<bke::Instances *> extract_instances(const bke::Instances &instances,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
using bke::Instances;
BLI_assert(mask.min_array_size() <= instances.instances_num());
Array<Instances *> elements(mask.size(), nullptr);
const bke::AttributeAccessor src_attributes = instances.attributes();
const Span<bke::InstanceReference> src_references = instances.references();
const Span<int> src_reference_handles = instances.reference_handles();
const Span<float4x4> src_transforms = instances.transforms();
mask.foreach_index(GrainSize(32), [&](const int instance_i, const int element_i) {
const int old_handle = src_reference_handles[instance_i];
const bke::InstanceReference &old_reference = src_references[old_handle];
const float4x4 &old_transform = src_transforms[instance_i];
Instances *element = new Instances();
const int new_handle = element->add_new_reference(old_reference);
element->add_instance(new_handle, old_transform);
bke::gather_attributes(src_attributes,
AttrDomain::Instance,
AttrDomain::Instance,
bke::attribute_filter_with_skip_ref(
attribute_filter, {".reference_index", "instance_transform"}),
Span<int>{instance_i},
element->attributes_for_write());
elements[element_i] = element;
});
return elements;
}
Array<GreasePencil *> extract_greasepencil_layers(const GreasePencil &grease_pencil,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
using namespace bke::greasepencil;
BLI_assert(mask.min_array_size() <= grease_pencil.layers().size());
Array<GreasePencil *> elements(mask.size(), nullptr);
const bke::AttributeAccessor src_attributes = grease_pencil.attributes();
const Span<const Layer *> src_layers = grease_pencil.layers();
mask.foreach_index(GrainSize(32), [&](const int layer_i, const int element_i) {
GreasePencil *element = BKE_grease_pencil_new_nomain();
element->material_array = static_cast<Material **>(
MEM_dupallocN(grease_pencil.material_array));
element->material_array_num = grease_pencil.material_array_num;
const Layer &src_layer = *src_layers[layer_i];
const Drawing *src_drawing = grease_pencil.get_eval_drawing(src_layer);
if (src_drawing) {
Layer &new_layer = element->add_layer(src_layer.name());
Drawing &drawing = *element->insert_frame(new_layer, element->runtime->eval_frame);
drawing.strokes_for_write() = src_drawing->strokes();
bke::gather_attributes(src_attributes,
AttrDomain::Layer,
AttrDomain::Layer,
attribute_filter,
Span<int>{layer_i},
element->attributes_for_write());
}
elements[element_i] = element;
});
return elements;
}
Array<GreasePencil *> extract_greasepencil_layer_points(
const GreasePencil &grease_pencil,
int layer_i,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
using namespace bke::greasepencil;
const Layer &src_layer = grease_pencil.layer(layer_i);
const Drawing &src_drawing = *grease_pencil.get_eval_drawing(src_layer);
const bke::CurvesGeometry &src_curves = src_drawing.strokes();
const bke::AttributeAccessor src_layer_attributes = grease_pencil.attributes();
const bke::AttributeAccessor src_curves_attributes = src_curves.attributes();
const Array<int> point_to_curve_map = src_curves.point_to_curve_map();
Array<GreasePencil *> elements(mask.size(), nullptr);
mask.foreach_index(GrainSize(32), [&](const int point_i, const int element_i) {
const int curve_i = point_to_curve_map[point_i];
GreasePencil *element = BKE_grease_pencil_new_nomain();
element->material_array = static_cast<Material **>(
MEM_dupallocN(grease_pencil.material_array));
element->material_array_num = grease_pencil.material_array_num;
Layer &new_layer = element->add_layer(src_layer.name());
Drawing &drawing = *element->insert_frame(new_layer, element->runtime->eval_frame);
bke::CurvesGeometry &new_curves = drawing.strokes_for_write();
new_curves.resize(1, 1);
new_curves.offsets_for_write().last() = 1;
bke::gather_attributes(src_curves_attributes,
AttrDomain::Point,
AttrDomain::Point,
attribute_filter,
Span<int>{point_i},
new_curves.attributes_for_write());
bke::gather_attributes(src_curves_attributes,
AttrDomain::Curve,
AttrDomain::Curve,
attribute_filter,
Span<int>{curve_i},
new_curves.attributes_for_write());
bke::gather_attributes(src_layer_attributes,
AttrDomain::Layer,
AttrDomain::Layer,
attribute_filter,
Span<int>{layer_i},
element->attributes_for_write());
new_curves.update_curve_types();
elements[element_i] = element;
});
return elements;
}
Array<GreasePencil *> extract_greasepencil_layer_curves(
const GreasePencil &grease_pencil,
const int layer_i,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
using namespace bke::greasepencil;
const Layer &src_layer = grease_pencil.layer(layer_i);
const Drawing &src_drawing = *grease_pencil.get_eval_drawing(src_layer);
const bke::CurvesGeometry &src_curves = src_drawing.strokes();
const bke::AttributeAccessor src_layer_attributes = grease_pencil.attributes();
const bke::AttributeAccessor src_curves_attributes = src_curves.attributes();
const OffsetIndices<int> src_points_by_curve = src_curves.points_by_curve();
Array<GreasePencil *> elements(mask.size(), nullptr);
mask.foreach_index(GrainSize(32), [&](const int curve_i, const int element_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const int points_num = src_points.size();
GreasePencil *element = BKE_grease_pencil_new_nomain();
element->material_array = static_cast<Material **>(
MEM_dupallocN(grease_pencil.material_array));
element->material_array_num = grease_pencil.material_array_num;
Layer &new_layer = element->add_layer(src_layer.name());
Drawing &drawing = *element->insert_frame(new_layer, element->runtime->eval_frame);
bke::CurvesGeometry &new_curves = drawing.strokes_for_write();
new_curves.resize(points_num, 1);
bke::gather_attributes(src_curves_attributes,
AttrDomain::Point,
AttrDomain::Point,
attribute_filter,
src_points,
new_curves.attributes_for_write());
bke::gather_attributes(src_curves_attributes,
AttrDomain::Curve,
AttrDomain::Curve,
attribute_filter,
Span<int>{curve_i},
new_curves.attributes_for_write());
bke::gather_attributes(src_layer_attributes,
AttrDomain::Layer,
AttrDomain::Layer,
attribute_filter,
Span<int>{layer_i},
element->attributes_for_write());
new_curves.update_curve_types();
elements[element_i] = element;
});
return elements;
}
} // namespace blender::geometry

View File

@@ -1973,6 +1973,79 @@ typedef struct NodeGeometryRepeatOutput {
#endif
} NodeGeometryRepeatOutput;
typedef struct NodeGeometryForeachGeometryElementInput {
/** bNode.identifier of the corresponding output node. */
int32_t output_node_id;
} NodeGeometryForeachGeometryElementInput;
typedef struct NodeForeachGeometryElementInputItem {
char *name;
/** #eNodeSocketDatatype. */
short socket_type;
char _pad[2];
/** Generated identifier that stays the same even when the name or order changes. */
int identifier;
} NodeForeachGeometryElementInputItem;
typedef struct NodeForeachGeometryElementMainItem {
char *name;
/** #eNodeSocketDatatype. */
short socket_type;
char _pad[2];
/** Generated identifier that stays the same even when the name or order changes. */
int identifier;
} NodeForeachGeometryElementMainItem;
typedef struct NodeForeachGeometryElementGenerationItem {
char *name;
/** #eNodeSocketDatatype. */
short socket_type;
/** #AttrDomain. */
uint8_t domain;
char _pad[1];
/** Generated identifier that stays the same even when the name or order changes. */
int identifier;
} NodeForeachGeometryElementGenerationItem;
typedef struct NodeForeachGeometryElementInputItems {
NodeForeachGeometryElementInputItem *items;
int items_num;
int active_index;
int next_identifier;
char _pad[4];
} NodeForeachGeometryElementInputItems;
typedef struct NodeForeachGeometryElementMainItems {
NodeForeachGeometryElementMainItem *items;
int items_num;
int active_index;
int next_identifier;
char _pad[4];
} NodeForeachGeometryElementMainItems;
typedef struct NodeForeachGeometryElementGenerationItems {
NodeForeachGeometryElementGenerationItem *items;
int items_num;
int active_index;
int next_identifier;
char _pad[4];
} NodeForeachGeometryElementGenerationItems;
typedef struct NodeGeometryForeachGeometryElementOutput {
/**
* The foreach zone has three sets of dynamic sockets.One on the input node and two on the
* output node. All settings are stored centrally in the output node storage though.
*/
NodeForeachGeometryElementInputItems input_items;
NodeForeachGeometryElementMainItems main_items;
NodeForeachGeometryElementGenerationItems generation_items;
/** This index is used when displaying socket values or using the viewer node. */
int inspection_index;
/** #AttrDomain. This is the domain that is iterated over. */
uint8_t domain;
char _pad[3];
} NodeGeometryForeachGeometryElementOutput;
typedef struct IndexSwitchItem {
/** Generated unique identifier which stays the same even when the item order or names change. */
int identifier;

View File

@@ -343,7 +343,6 @@ typedef struct ThemeSpace {
unsigned char syntaxd[4], syntaxr[4]; /* In node-space used for distort. */
unsigned char line_numbers[4];
char _pad6[3];
unsigned char nodeclass_output[4], nodeclass_filter[4];
unsigned char nodeclass_vector[4], nodeclass_texture[4];
@@ -353,6 +352,7 @@ typedef struct ThemeSpace {
unsigned char node_zone_simulation[4];
unsigned char node_zone_repeat[4];
unsigned char node_zone_foreach_geometry_element[4];
unsigned char simulated_frames[4];
/** For sequence editor. */
@@ -361,7 +361,6 @@ typedef struct ThemeSpace {
unsigned char active_strip[4], selected_strip[4];
/** For dopesheet - scale factor for size of keyframes (i.e. height of channels). */
char _pad7[1];
float keyframe_scale_fac;
unsigned char editmesh_active[4];

View File

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

View File

@@ -639,6 +639,7 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = {
# include "NOD_composite.hh"
# include "NOD_geo_bake.hh"
# include "NOD_geo_capture_attribute.hh"
# include "NOD_geo_foreach_geometry_element.hh"
# include "NOD_geo_index_switch.hh"
# include "NOD_geo_menu_switch.hh"
# include "NOD_geo_repeat.hh"
@@ -657,6 +658,9 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = {
using blender::nodes::BakeItemsAccessor;
using blender::nodes::CaptureAttributeItemsAccessor;
using blender::nodes::ForeachGeometryElementGenerationItemsAccessor;
using blender::nodes::ForeachGeometryElementInputItemsAccessor;
using blender::nodes::ForeachGeometryElementMainItemsAccessor;
using blender::nodes::IndexSwitchItemsAccessor;
using blender::nodes::MenuSwitchItemsAccessor;
using blender::nodes::RepeatItemsAccessor;
@@ -9466,6 +9470,13 @@ static void def_geo_repeat_input(StructRNA *srna)
def_common_zone_input(srna);
}
static void def_geo_foreach_geometry_element_input(StructRNA *srna)
{
RNA_def_struct_sdna_from(srna, "NodeGeometryForeachGeometryElementInput", "storage");
def_common_zone_input(srna);
}
static void rna_def_node_item_array_socket_item_common(StructRNA *srna,
const char *accessor,
const bool add_socket_type)
@@ -9703,6 +9714,151 @@ static void def_geo_repeat_output(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE, "rna_Node_update");
}
static void rna_def_geo_foreach_geometry_element_input_item(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "ForeachGeometryElementInputItem", nullptr);
RNA_def_struct_ui_text(srna, "For Each Geometry Element Item", "");
RNA_def_struct_sdna(srna, "NodeForeachGeometryElementInputItem");
rna_def_node_item_array_socket_item_common(
srna, "ForeachGeometryElementInputItemsAccessor", true);
}
static void rna_def_geo_foreach_geometry_element_input_items(BlenderRNA *brna)
{
StructRNA *srna;
srna = RNA_def_struct(brna, "NodeGeometryForeachGeometryElementInputItems", nullptr);
RNA_def_struct_sdna(srna, "bNode");
RNA_def_struct_ui_text(srna, "Input Items", "Collection of input items");
rna_def_node_item_array_new_with_socket_and_name(
srna, "ForeachGeometryElementInputItem", "ForeachGeometryElementInputItemsAccessor");
rna_def_node_item_array_common_functions(
srna, "ForeachGeometryElementInputItem", "ForeachGeometryElementInputItemsAccessor");
}
static void rna_def_geo_foreach_geometry_element_main_item(BlenderRNA *brna)
{
StructRNA *srna;
srna = RNA_def_struct(brna, "ForeachGeometryElementMainItem", nullptr);
RNA_def_struct_ui_text(srna, "For Each Geometry Element Item", "");
RNA_def_struct_sdna(srna, "NodeForeachGeometryElementMainItem");
rna_def_node_item_array_socket_item_common(
srna, "ForeachGeometryElementMainItemsAccessor", true);
}
static void rna_def_geo_foreach_geometry_element_main_items(BlenderRNA *brna)
{
StructRNA *srna;
srna = RNA_def_struct(brna, "NodeGeometryForeachGeometryElementMainItems", nullptr);
RNA_def_struct_sdna(srna, "bNode");
RNA_def_struct_ui_text(srna, "Main Items", "Collection of main items");
rna_def_node_item_array_new_with_socket_and_name(
srna, "ForeachGeometryElementMainItem", "ForeachGeometryElementMainItemsAccessor");
rna_def_node_item_array_common_functions(
srna, "ForeachGeometryElementMainItem", "ForeachGeometryElementMainItemsAccessor");
}
static void rna_def_geo_foreach_geometry_element_generation_item(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "ForeachGeometryElementGenerationItem", nullptr);
RNA_def_struct_ui_text(srna, "For Each Geometry Element Item", "");
RNA_def_struct_sdna(srna, "NodeForeachGeometryElementGenerationItem");
rna_def_node_item_array_socket_item_common(
srna, "ForeachGeometryElementGenerationItemsAccessor", true);
prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
RNA_def_property_ui_text(prop, "Domain", "Domain that the field is evaluated on");
RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items);
RNA_def_property_update(
prop,
NC_NODE | NA_EDITED,
"rna_Node_ItemArray_item_update<ForeachGeometryElementGenerationItemsAccessor>");
}
static void rna_def_geo_foreach_geometry_element_generation_items(BlenderRNA *brna)
{
StructRNA *srna;
srna = RNA_def_struct(brna, "NodeGeometryForeachGeometryElementGenerationItems", nullptr);
RNA_def_struct_sdna(srna, "bNode");
RNA_def_struct_ui_text(srna, "Generation Items", "Collection of generation items");
rna_def_node_item_array_new_with_socket_and_name(
srna,
"ForeachGeometryElementGenerationItem",
"ForeachGeometryElementGenerationItemsAccessor");
rna_def_node_item_array_common_functions(srna,
"ForeachGeometryElementGenerationItem",
"ForeachGeometryElementGenerationItemsAccessor");
}
static void def_geo_foreach_geometry_element_output(StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeGeometryForeachGeometryElementOutput", "storage");
prop = RNA_def_property(srna, "input_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "input_items.items", "input_items.items_num");
RNA_def_property_struct_type(prop, "ForeachGeometryElementInputItem");
RNA_def_property_srna(prop, "NodeGeometryForeachGeometryElementInputItems");
prop = RNA_def_property(srna, "main_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "main_items.items", "main_items.items_num");
RNA_def_property_struct_type(prop, "ForeachGeometryElementMainItem");
RNA_def_property_srna(prop, "NodeGeometryForeachGeometryElementMainItems");
prop = RNA_def_property(srna, "generation_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(
prop, nullptr, "generation_items.items", "generation_items.items_num");
RNA_def_property_struct_type(prop, "ForeachGeometryElementGenerationItem");
RNA_def_property_srna(prop, "NodeGeometryForeachGeometryElementGenerationItems");
prop = RNA_def_property(srna, "active_input_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "input_items.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_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_NODE, nullptr);
prop = RNA_def_property(srna, "active_generation_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "generation_items.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_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_NODE, nullptr);
prop = RNA_def_property(srna, "active_main_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "main_items.active_index");
RNA_def_property_ui_text(prop, "Active Main Item Index", "Index of the active item");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_NODE, nullptr);
prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
RNA_def_property_ui_text(prop, "Domain", "Geometry domain that is iterated over");
RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items);
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "inspection_index", PROP_INT, PROP_NONE);
RNA_def_property_ui_range(prop, 0, INT32_MAX, 1, -1);
RNA_def_property_ui_text(prop,
"Inspection Index",
"Iteration index that is used by inspection features like the viewer "
"node or socket inspection");
RNA_def_property_update(prop, NC_NODE, "rna_Node_update");
}
static void rna_def_geo_capture_attribute_item(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometryCaptureAttributeItem", nullptr);
@@ -11408,6 +11564,9 @@ void RNA_def_nodetree(BlenderRNA *brna)
rna_def_simulation_state_item(brna);
rna_def_repeat_item(brna);
rna_def_geo_foreach_geometry_element_input_item(brna);
rna_def_geo_foreach_geometry_element_main_item(brna);
rna_def_geo_foreach_geometry_element_generation_item(brna);
rna_def_index_switch_item(brna);
rna_def_menu_switch_item(brna);
rna_def_geo_bake_item(brna);
@@ -11464,6 +11623,9 @@ void RNA_def_nodetree(BlenderRNA *brna)
rna_def_cmp_output_file_slot_layer(brna);
rna_def_geo_simulation_output_items(brna);
rna_def_geo_repeat_output_items(brna);
rna_def_geo_foreach_geometry_element_input_items(brna);
rna_def_geo_foreach_geometry_element_main_items(brna);
rna_def_geo_foreach_geometry_element_generation_items(brna);
rna_def_geo_index_switch_items(brna);
rna_def_geo_menu_switch_items(brna);
rna_def_bake_items(brna);

View File

@@ -3494,6 +3494,8 @@ static StructRNA *rna_viewer_path_elem_refine(PointerRNA *ptr)
return &RNA_ViewerNodeViewerPathElem;
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE:
return &RNA_RepeatZoneViewerPathElem;
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE:
return &RNA_ForeachGeometryElementZoneViewerPathElem;
}
BLI_assert_unreachable();
return nullptr;
@@ -8366,6 +8368,11 @@ static const EnumPropertyItem viewer_path_elem_type_items[] = {
{VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE, "SIMULATION_ZONE", ICON_NONE, "Simulation Zone", ""},
{VIEWER_PATH_ELEM_TYPE_VIEWER_NODE, "VIEWER_NODE", ICON_NONE, "Viewer Node", ""},
{VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE, "REPEAT_ZONE", ICON_NONE, "Repeat", ""},
{VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE,
"FOREACH_GEOMETRY_ELEMENT_ZONE",
ICON_NONE,
"For Each Geometry Element",
""},
{0, nullptr, 0, nullptr, nullptr},
};
@@ -8444,6 +8451,17 @@ static void rna_def_repeat_zone_viewer_path_elem(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Repeat Output Node ID", "");
}
static void rna_def_foreach_geometry_element_zone_viewer_path_elem(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "ForeachGeometryElementZoneViewerPathElem", "ViewerPathElem");
prop = RNA_def_property(srna, "zone_output_node_id", PROP_INT, PROP_NONE);
RNA_def_property_ui_text(prop, "Zone Output Node ID", "");
}
static void rna_def_viewer_node_viewer_path_elem(BlenderRNA *brna)
{
StructRNA *srna;
@@ -8466,6 +8484,7 @@ static void rna_def_viewer_path(BlenderRNA *brna)
rna_def_group_node_viewer_path_elem(brna);
rna_def_simulation_zone_viewer_path_elem(brna);
rna_def_repeat_zone_viewer_path_elem(brna);
rna_def_foreach_geometry_element_zone_viewer_path_elem(brna);
rna_def_viewer_node_viewer_path_elem(brna);
srna = RNA_def_struct(brna, "ViewerPath", nullptr);

View File

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

View File

@@ -571,11 +571,34 @@ static void try_add_side_effect_node(const ComputeContext &final_compute_context
return;
}
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
local_side_effect_nodes.iterations_by_repeat_zone.add(
local_side_effect_nodes.iterations_by_iteration_zone.add(
{parent_compute_context_hash, compute_context->output_node_id()},
compute_context->iteration());
current_zone = repeat_zone;
}
else if (const auto *compute_context =
dynamic_cast<const bke::ForeachGeometryElementZoneComputeContext *>(
compute_context_generic))
{
const bke::bNodeTreeZone *foreach_zone = current_zones->get_zone_by_node(
compute_context->output_node_id());
if (foreach_zone == nullptr) {
return;
}
if (foreach_zone->parent_zone != current_zone) {
return;
}
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
foreach_zone, nullptr);
if (lf_zone_node == nullptr) {
return;
}
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
local_side_effect_nodes.iterations_by_iteration_zone.add(
{parent_compute_context_hash, compute_context->output_node_id()},
compute_context->index());
current_zone = foreach_zone;
}
else if (const auto *compute_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
compute_context_generic))
{
@@ -631,8 +654,8 @@ static void try_add_side_effect_node(const ComputeContext &final_compute_context
for (const auto item : local_side_effect_nodes.nodes_by_context.items()) {
r_side_effect_nodes.nodes_by_context.add_multiple(item.key, item.value);
}
for (const auto item : local_side_effect_nodes.iterations_by_repeat_zone.items()) {
r_side_effect_nodes.iterations_by_repeat_zone.add_multiple(item.key, item.value);
for (const auto item : local_side_effect_nodes.iterations_by_iteration_zone.items()) {
r_side_effect_nodes.iterations_by_iteration_zone.add_multiple(item.key, item.value);
}
}

View File

@@ -150,10 +150,10 @@ class GeoNodesBakeParams {
struct GeoNodesSideEffectNodes {
MultiValueMap<ComputeContextHash, const lf::FunctionNode *> nodes_by_context;
/**
* The repeat zone is identified by the compute context of the parent and the identifier of the
* repeat output node.
* The repeat/foreach zone is identified by the compute context of the parent and the identifier
* of the repeat output node.
*/
MultiValueMap<std::pair<ComputeContextHash, int32_t>, int> iterations_by_repeat_zone;
MultiValueMap<std::pair<ComputeContextHash, int32_t>, int> iterations_by_iteration_zone;
};
/**

View File

@@ -275,10 +275,12 @@ template<typename Accessor>
* \return False if the link should be removed.
*/
template<typename Accessor>
[[nodiscard]] inline bool try_add_item_via_any_extend_socket(bNodeTree &ntree,
bNode &extend_node,
bNode &storage_node,
bNodeLink &link)
[[nodiscard]] inline bool try_add_item_via_any_extend_socket(
bNodeTree &ntree,
bNode &extend_node,
bNode &storage_node,
bNodeLink &link,
const std::optional<StringRef> socket_identifier = std::nullopt)
{
bNodeSocket *possible_extend_socket = nullptr;
if (link.fromnode == &extend_node) {
@@ -293,6 +295,11 @@ template<typename Accessor>
if (!STREQ(possible_extend_socket->idname, "NodeSocketVirtual")) {
return true;
}
if (socket_identifier.has_value()) {
if (possible_extend_socket->identifier != socket_identifier) {
return true;
}
}
return try_add_item_via_extend_socket<Accessor>(
ntree, extend_node, *possible_extend_socket, storage_node, link);
}

View File

@@ -352,6 +352,8 @@ DefNode(GeometryNode, GEO_NODE_EXTRUDE_MESH, 0, "EXTRUDE_MESH", ExtrudeMesh, "Ex
DefNode(GeometryNode, GEO_NODE_FILL_CURVE, 0, "FILL_CURVE", FillCurve, "Fill Curve", "Generate a mesh on the XY plane with faces on the inside of input curves")
DefNode(GeometryNode, GEO_NODE_FILLET_CURVE, 0, "FILLET_CURVE", FilletCurve, "Fillet Curve", "Round corners by generating circular arcs on each control point")
DefNode(GeometryNode, GEO_NODE_FLIP_FACES, 0, "FLIP_FACES", FlipFaces, "Flip Faces", "Reverse the order of the vertices and edges of selected faces, flipping their normal direction")
DefNode(GeometryNode, GEO_NODE_FOREACH_GEOMETRY_ELEMENT_INPUT, def_geo_foreach_geometry_element_input, "FOREACH_GEOMETRY_ELEMENT_INPUT", ForeachGeometryElementInput, "For Each Geometry Element Input", "")
DefNode(GeometryNode, GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT, def_geo_foreach_geometry_element_output, "FOREACH_GEOMETRY_ELEMENT_OUTPUT", ForeachGeometryElementOutput, "For Each Geometry Element Output", "")
DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE", GeometryToInstance, "Geometry to Instance", "Convert each input geometry into an instance, which can be much faster than the Join Geometry node when the inputs are large")
DefNode(GeometryNode, GEO_NODE_GET_NAMED_GRID, 0, "GET_NAMED_GRID", GetNamedGrid, "Get Named Grid", "Get volume grid from a volume geometry with the specified name")
DefNode(GeometryNode, GEO_NODE_GIZMO_LINEAR, 0, "GIZMO_LINEAR", GizmoLinear, "Linear Gizmo", "Show a linear gizmo in the viewport for a value")

View File

@@ -84,6 +84,7 @@ set(SRC
nodes/node_geo_evaluate_on_domain.cc
nodes/node_geo_extrude_mesh.cc
nodes/node_geo_flip_faces.cc
nodes/node_geo_foreach_geometry_element.cc
nodes/node_geo_geometry_to_instance.cc
nodes/node_geo_get_named_grid.cc
nodes/node_geo_gizmo_dial.cc

View File

@@ -0,0 +1,227 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DNA_node_types.h"
#include "NOD_socket_items.hh"
namespace blender::nodes {
struct ForeachGeometryElementInputItemsAccessor {
using ItemT = NodeForeachGeometryElementInputItem;
static StructRNA *item_srna;
static int node_type;
static constexpr const char *node_idname = "GeometryNodeForeachGeometryElementOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = true;
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryForeachGeometryElementOutput *>(node.storage);
return {&storage->input_items.items,
&storage->input_items.items_num,
&storage->input_items.active_index};
}
static void copy_item(const ItemT &src, ItemT &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(ItemT *item)
{
MEM_SAFE_FREE(item->name);
}
static void blend_write(BlendWriter *writer, const bNode &node);
static void blend_read_data(BlendDataReader *reader, bNode &node);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return eNodeSocketDatatype(item.socket_type);
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
{
return ELEM(socket_type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_INT);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryForeachGeometryElementOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->generation_items.next_identifier++;
socket_items::set_item_name_and_make_unique<ForeachGeometryElementInputItemsAccessor>(
node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Input_" + std::to_string(item.identifier);
}
};
struct ForeachGeometryElementMainItemsAccessor {
using ItemT = NodeForeachGeometryElementMainItem;
static StructRNA *item_srna;
static int node_type;
static constexpr const char *node_idname = "GeometryNodeForeachGeometryElementOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = true;
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryForeachGeometryElementOutput *>(node.storage);
return {&storage->main_items.items,
&storage->main_items.items_num,
&storage->main_items.active_index};
}
static void copy_item(const ItemT &src, ItemT &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(ItemT *item)
{
MEM_SAFE_FREE(item->name);
}
static void blend_write(BlendWriter *writer, const bNode &node);
static void blend_read_data(BlendDataReader *reader, bNode &node);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return eNodeSocketDatatype(item.socket_type);
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
{
return ELEM(socket_type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_INT);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryForeachGeometryElementOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->generation_items.next_identifier++;
socket_items::set_item_name_and_make_unique<ForeachGeometryElementMainItemsAccessor>(
node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Main_" + std::to_string(item.identifier);
}
};
struct ForeachGeometryElementGenerationItemsAccessor {
using ItemT = NodeForeachGeometryElementGenerationItem;
static StructRNA *item_srna;
static int node_type;
static constexpr const char *node_idname = "GeometryNodeForeachGeometryElementOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = true;
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryForeachGeometryElementOutput *>(node.storage);
return {&storage->generation_items.items,
&storage->generation_items.items_num,
&storage->generation_items.active_index};
}
static void copy_item(const ItemT &src, ItemT &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(ItemT *item)
{
MEM_SAFE_FREE(item->name);
}
static void blend_write(BlendWriter *writer, const bNode &node);
static void blend_read_data(BlendDataReader *reader, bNode &node);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return eNodeSocketDatatype(item.socket_type);
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
{
return ELEM(socket_type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_INT,
SOCK_GEOMETRY);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryForeachGeometryElementOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->generation_items.next_identifier++;
socket_items::set_item_name_and_make_unique<ForeachGeometryElementGenerationItemsAccessor>(
node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Generation_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@@ -0,0 +1,749 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "BLI_string_utf8.h"
#include "BLO_read_write.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "NOD_geo_foreach_geometry_element.hh"
#include "NOD_node_extra_info.hh"
#include "NOD_socket_items_ops.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "BKE_screen.hh"
#include "WM_api.hh"
namespace blender::nodes::node_geo_foreach_geometry_element_cc {
static void draw_item(uiList * /*ui_list*/,
const bContext *C,
uiLayout *layout,
PointerRNA * /*idataptr*/,
PointerRNA *itemptr,
int /*icon*/,
PointerRNA * /*active_dataptr*/,
const char * /*active_propname*/,
int /*index*/,
int /*flt_flag*/)
{
uiLayout *row = uiLayoutRow(layout, true);
float4 color;
RNA_float_get_array(itemptr, "color", color);
uiTemplateNodeSocket(row, const_cast<bContext *>(C), color);
uiLayoutSetEmboss(row, UI_EMBOSS_NONE);
uiItemR(row, itemptr, "name", UI_ITEM_NONE, "", ICON_NONE);
}
/** Shared between zone input and output node. */
static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *current_node_ptr)
{
bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(current_node_ptr->owner_id);
bNode *current_node = static_cast<bNode *>(current_node_ptr->data);
const bke::bNodeTreeZones *zones = ntree.zones();
if (!zones) {
return;
}
const bke::bNodeTreeZone *zone = zones->get_zone_by_node(current_node->identifier);
if (!zone) {
return;
}
if (!zone->output_node) {
return;
}
const bool is_zone_input_node = current_node->type == GEO_NODE_FOREACH_GEOMETRY_ELEMENT_INPUT;
bNode &output_node = const_cast<bNode &>(*zone->output_node);
PointerRNA output_node_ptr = RNA_pointer_create(
current_node_ptr->owner_id, &RNA_Node, &output_node);
auto &storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(output_node.storage);
if (is_zone_input_node) {
if (uiLayout *panel = uiLayoutPanel(C, layout, "input", false, TIP_("Input Fields"))) {
static const uiListType *input_items_list = []() {
uiListType *list = MEM_cnew<uiListType>(__func__);
STRNCPY(list->idname, "DATA_UL_foreach_geometry_element_input_items");
list->draw_item = draw_item;
WM_uilisttype_add(list);
return list;
}();
uiLayout *row = uiLayoutRow(panel, false);
uiTemplateList(row,
C,
input_items_list->idname,
"",
&output_node_ptr,
"input_items",
&output_node_ptr,
"active_input_index",
nullptr,
3,
5,
UILST_LAYOUT_DEFAULT,
0,
UI_TEMPLATE_LIST_FLAG_NONE);
{
uiLayout *ops_col = uiLayoutColumn(row, false);
{
uiLayout *add_remove_col = uiLayoutColumn(ops_col, true);
uiItemO(
add_remove_col, "", ICON_ADD, "node.foreach_geometry_element_zone_input_item_add");
uiItemO(add_remove_col,
"",
ICON_REMOVE,
"node.foreach_geometry_element_zone_input_item_remove");
}
{
uiLayout *up_down_col = uiLayoutColumn(ops_col, true);
uiItemEnumO(up_down_col,
"node.foreach_geometry_element_zone_input_item_move",
"",
ICON_TRIA_UP,
"direction",
0);
uiItemEnumO(up_down_col,
"node.foreach_geometry_element_zone_input_item_move",
"",
ICON_TRIA_DOWN,
"direction",
1);
}
}
if (storage.input_items.active_index >= 0 &&
storage.input_items.active_index < storage.input_items.items_num)
{
NodeForeachGeometryElementInputItem &active_item =
storage.input_items.items[storage.input_items.active_index];
PointerRNA item_ptr = RNA_pointer_create(
output_node_ptr.owner_id,
ForeachGeometryElementInputItemsAccessor::item_srna,
&active_item);
uiLayoutSetPropSep(panel, true);
uiLayoutSetPropDecorate(panel, false);
uiItemR(panel, &item_ptr, "socket_type", UI_ITEM_NONE, nullptr, ICON_NONE);
}
}
}
else {
if (uiLayout *panel = uiLayoutPanel(C, layout, "main_items", false, TIP_("Main Geometry"))) {
static const uiListType *main_items_list = []() {
uiListType *list = MEM_cnew<uiListType>(__func__);
STRNCPY(list->idname, "DATA_UL_foreach_geometry_element_main_items");
list->draw_item = draw_item;
WM_uilisttype_add(list);
return list;
}();
uiLayout *row = uiLayoutRow(panel, false);
uiTemplateList(row,
C,
main_items_list->idname,
"",
&output_node_ptr,
"main_items",
&output_node_ptr,
"active_main_index",
nullptr,
3,
5,
UILST_LAYOUT_DEFAULT,
0,
UI_TEMPLATE_LIST_FLAG_NONE);
{
uiLayout *ops_col = uiLayoutColumn(row, false);
{
uiLayout *add_remove_col = uiLayoutColumn(ops_col, true);
uiItemO(
add_remove_col, "", ICON_ADD, "node.foreach_geometry_element_zone_main_item_add");
uiItemO(add_remove_col,
"",
ICON_REMOVE,
"node.foreach_geometry_element_zone_main_item_remove");
}
{
uiLayout *up_down_col = uiLayoutColumn(ops_col, true);
uiItemEnumO(up_down_col,
"node.foreach_geometry_element_zone_main_item_move",
"",
ICON_TRIA_UP,
"direction",
0);
uiItemEnumO(up_down_col,
"node.foreach_geometry_element_zone_main_item_move",
"",
ICON_TRIA_DOWN,
"direction",
1);
}
}
if (storage.main_items.active_index >= 0 &&
storage.main_items.active_index < storage.main_items.items_num)
{
NodeForeachGeometryElementMainItem &active_item =
storage.main_items.items[storage.main_items.active_index];
PointerRNA item_ptr = RNA_pointer_create(
output_node_ptr.owner_id,
ForeachGeometryElementMainItemsAccessor::item_srna,
&active_item);
uiLayoutSetPropSep(panel, true);
uiLayoutSetPropDecorate(panel, false);
uiItemR(panel, &item_ptr, "socket_type", UI_ITEM_NONE, nullptr, ICON_NONE);
}
}
if (uiLayout *panel = uiLayoutPanel(
C, layout, "generation_items", false, TIP_("Generated Geometry")))
{
static const uiListType *generation_items_list = []() {
uiListType *list = MEM_cnew<uiListType>(__func__);
STRNCPY(list->idname, "DATA_UL_foreach_geometry_element_generation_items");
list->draw_item = draw_item;
WM_uilisttype_add(list);
return list;
}();
uiLayout *row = uiLayoutRow(panel, false);
uiTemplateList(row,
C,
generation_items_list->idname,
"",
&output_node_ptr,
"generation_items",
&output_node_ptr,
"active_generation_index",
nullptr,
3,
5,
UILST_LAYOUT_DEFAULT,
0,
UI_TEMPLATE_LIST_FLAG_NONE);
{
uiLayout *ops_col = uiLayoutColumn(row, false);
{
uiLayout *add_remove_col = uiLayoutColumn(ops_col, true);
uiItemO(add_remove_col,
"",
ICON_ADD,
"node.foreach_geometry_element_zone_generation_item_add");
uiItemO(add_remove_col,
"",
ICON_REMOVE,
"node.foreach_geometry_element_zone_generation_item_remove");
}
{
uiLayout *up_down_col = uiLayoutColumn(ops_col, true);
uiItemEnumO(up_down_col,
"node.foreach_geometry_element_zone_generation_item_move",
"",
ICON_TRIA_UP,
"direction",
0);
uiItemEnumO(up_down_col,
"node.foreach_geometry_element_zone_generation_item_move",
"",
ICON_TRIA_DOWN,
"direction",
1);
}
}
if (storage.generation_items.active_index >= 0 &&
storage.generation_items.active_index < storage.generation_items.items_num)
{
NodeForeachGeometryElementGenerationItem &active_item =
storage.generation_items.items[storage.generation_items.active_index];
PointerRNA item_ptr = RNA_pointer_create(
output_node_ptr.owner_id,
ForeachGeometryElementGenerationItemsAccessor::item_srna,
&active_item);
uiLayoutSetPropSep(panel, true);
uiLayoutSetPropDecorate(panel, false);
uiItemR(panel, &item_ptr, "socket_type", UI_ITEM_NONE, nullptr, ICON_NONE);
if (active_item.socket_type != SOCK_GEOMETRY) {
uiItemR(panel, &item_ptr, "domain", UI_ITEM_NONE, nullptr, ICON_NONE);
}
}
}
}
uiItemR(layout, &output_node_ptr, "inspection_index", UI_ITEM_NONE, nullptr, ICON_NONE);
}
namespace input_node {
NODE_STORAGE_FUNCS(NodeGeometryForeachGeometryElementInput);
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
const bNode *node = b.node_or_null();
const bNodeTree *tree = b.tree_or_null();
if (!node || !tree) {
return;
}
const NodeGeometryForeachGeometryElementInput &storage = node_storage(*node);
const bNode *output_node = tree->node_by_id(storage.output_node_id);
const auto &output_storage = output_node ?
static_cast<const NodeGeometryForeachGeometryElementOutput *>(
output_node->storage) :
nullptr;
b.add_output<decl::Int>("Index").description(
"Index of the element in the source geometry. Note that the same index can occure more than "
"once when iterating over multiple components at once");
b.add_output<decl::Geometry>("Element")
.description(
"Single element geometry for the current iteration. Note that it can be quite "
"inefficient to splitup large geometries into many small geometries")
.propagate_all()
.available(output_storage && AttrDomain(output_storage->domain) != AttrDomain::Corner);
b.add_input<decl::Geometry>("Geometry").description("Geometry whose elements are iterated over");
b.add_input<decl::Bool>("Selection")
.default_value(true)
.hide_value()
.field_on_all()
.description("Selection on the iteration domain");
if (output_storage) {
for (const int i : IndexRange(output_storage->input_items.items_num)) {
const NodeForeachGeometryElementInputItem &item = output_storage->input_items.items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const StringRef name = item.name ? item.name : "";
const std::string identifier =
ForeachGeometryElementInputItemsAccessor::socket_identifier_for_item(item);
auto &input_decl = b.add_input(socket_type, name, identifier)
.socket_name_ptr(&tree->id,
ForeachGeometryElementInputItemsAccessor::item_srna,
&item,
"name")
.description("Field that is evaluated on the iteration domain");
b.add_output(socket_type, name, identifier)
.align_with_previous()
.description("Evaluated field value for the current element");
input_decl.supports_field();
}
}
b.add_input<decl::Extend>("", "__extend__");
b.add_output<decl::Extend>("", "__extend__").align_with_previous();
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode &node = *static_cast<bNode *>(ptr->data);
const NodeGeometryForeachGeometryElementInput &storage = node_storage(node);
bNode *output_node = tree.node_by_id(storage.output_node_id);
PointerRNA output_node_ptr = RNA_pointer_create(ptr->owner_id, &RNA_Node, output_node);
uiItemR(layout, &output_node_ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryForeachGeometryElementInput *data =
MEM_cnew<NodeGeometryForeachGeometryElementInput>(__func__);
/* Needs to be initialized for the node to work. */
data->output_node_id = 0;
node->storage = data;
}
static void node_label(const bNodeTree * /*ntree*/,
const bNode * /*node*/,
char *label,
const int label_maxncpy)
{
BLI_strncpy_utf8(label, IFACE_("For Each Element"), label_maxncpy);
}
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
{
bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id);
if (!output_node) {
return true;
}
return socket_items::try_add_item_via_any_extend_socket<
ForeachGeometryElementInputItemsAccessor>(*ntree, *node, *output_node, *link);
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype,
GEO_NODE_FOREACH_GEOMETRY_ELEMENT_INPUT,
"For Each Geometry Element Input",
NODE_CLASS_INTERFACE);
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.draw_buttons_ex = node_layout_ex;
ntype.labelfunc = node_label;
ntype.insert_link = node_insert_link;
ntype.gather_link_search_ops = nullptr;
ntype.no_muting = true;
blender::bke::node_type_storage(&ntype,
"NodeGeometryForeachGeometryElementInput",
node_free_standard_storage,
node_copy_standard_storage);
blender::bke::node_register_type(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace input_node
namespace output_node {
NODE_STORAGE_FUNCS(NodeGeometryForeachGeometryElementOutput);
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_output<decl::Geometry>("Geometry")
.description(
"The original input geometry with potentially new attributes that are output by the "
"zone");
aal::RelationsInNode &relations = b.get_anonymous_attribute_relations();
const bNode *node = b.node_or_null();
const bNodeTree *tree = b.tree_or_null();
if (node && tree) {
const NodeGeometryForeachGeometryElementOutput &storage = node_storage(*node);
for (const int i : IndexRange(storage.main_items.items_num)) {
const NodeForeachGeometryElementMainItem &item = storage.main_items.items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const StringRef name = item.name ? item.name : "";
std::string identifier = ForeachGeometryElementMainItemsAccessor::socket_identifier_for_item(
item);
b.add_input(socket_type, name, identifier)
.socket_name_ptr(
&tree->id, ForeachGeometryElementMainItemsAccessor::item_srna, &item, "name")
.description(
"Attribute value that will be stored for the current element on the main geometry");
b.add_output(socket_type, name, identifier)
.align_with_previous()
.field_on({0})
.description("Attribute on the geometry above");
}
b.add_input<decl::Extend>("", "__extend__main");
b.add_output<decl::Extend>("", "__extend__main").align_with_previous();
b.add_separator();
int previous_geometry_index = -1;
for (const int i : IndexRange(storage.generation_items.items_num)) {
const NodeForeachGeometryElementGenerationItem &item = storage.generation_items.items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
if (socket_type == SOCK_GEOMETRY && i > 0) {
b.add_separator();
}
const StringRef name = item.name ? item.name : "";
std::string identifier =
ForeachGeometryElementGenerationItemsAccessor::socket_identifier_for_item(item);
auto &input_decl = b.add_input(socket_type, name, identifier)
.socket_name_ptr(
&tree->id,
ForeachGeometryElementGenerationItemsAccessor::item_srna,
&item,
"name");
auto &output_decl = b.add_output(socket_type, name, identifier).align_with_previous();
if (socket_type == SOCK_GEOMETRY) {
previous_geometry_index = output_decl.index();
aal::PropagateRelation relation;
relation.from_geometry_input = input_decl.index();
relation.to_geometry_output = output_decl.index();
relations.propagate_relations.append(relation);
input_decl.description(
"Geometry generated in the current iteration. Will be joined with geometries from all "
"other iterations");
output_decl.description("Result of joining generated geometries from each iteration");
}
else {
input_decl.supports_field();
if (previous_geometry_index > 0) {
input_decl.description("Field that will be stored as attribute on the geometry above");
output_decl.field_on({previous_geometry_index});
}
output_decl.description("Attribute on the geometry above");
}
}
}
b.add_input<decl::Extend>("", "__extend__generation");
b.add_output<decl::Extend>("", "__extend__generation").align_with_previous();
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryForeachGeometryElementOutput *data =
MEM_cnew<NodeGeometryForeachGeometryElementOutput>(__func__);
data->generation_items.items = MEM_cnew_array<NodeForeachGeometryElementGenerationItem>(
1, __func__);
NodeForeachGeometryElementGenerationItem &item = data->generation_items.items[0];
item.name = BLI_strdup(DATA_("Geometry"));
item.socket_type = SOCK_GEOMETRY;
item.identifier = data->generation_items.next_identifier++;
data->generation_items.items_num = 1;
node->storage = data;
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<ForeachGeometryElementInputItemsAccessor>(*node);
socket_items::destruct_array<ForeachGeometryElementGenerationItemsAccessor>(*node);
socket_items::destruct_array<ForeachGeometryElementMainItemsAccessor>(*node);
MEM_freeN(node->storage);
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeGeometryForeachGeometryElementOutput &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_cnew<NodeGeometryForeachGeometryElementOutput>(__func__, src_storage);
dst_node->storage = dst_storage;
socket_items::copy_array<ForeachGeometryElementInputItemsAccessor>(*src_node, *dst_node);
socket_items::copy_array<ForeachGeometryElementGenerationItemsAccessor>(*src_node, *dst_node);
socket_items::copy_array<ForeachGeometryElementMainItemsAccessor>(*src_node, *dst_node);
}
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
{
if (!socket_items::try_add_item_via_any_extend_socket<ForeachGeometryElementMainItemsAccessor>(
*ntree, *node, *node, *link, "__extend__main"))
{
return false;
}
return socket_items::try_add_item_via_any_extend_socket<
ForeachGeometryElementGenerationItemsAccessor>(*ntree, *node, *node, *link);
}
static void NODE_OT_foreach_geometry_element_zone_input_item_remove(wmOperatorType *ot)
{
socket_items::ops::remove_active_item<ForeachGeometryElementInputItemsAccessor>(
ot, "Remove For Each Input Item", __func__, "Remove active for-each input item");
}
static void NODE_OT_foreach_geometry_element_zone_input_item_add(wmOperatorType *ot)
{
socket_items::ops::add_item<ForeachGeometryElementInputItemsAccessor>(
ot, "Add For Each Input Item", __func__, "Add for-each input item");
}
static void NODE_OT_foreach_geometry_element_zone_input_item_move(wmOperatorType *ot)
{
socket_items::ops::move_active_item<ForeachGeometryElementInputItemsAccessor>(
ot, "Move For Each Input Item", __func__, "Move active for-each input item");
}
static void NODE_OT_foreach_geometry_element_zone_generation_item_remove(wmOperatorType *ot)
{
socket_items::ops::remove_active_item<ForeachGeometryElementGenerationItemsAccessor>(
ot, "Remove For Each Generation Item", __func__, "Remove active for-each generation item");
}
static void NODE_OT_foreach_geometry_element_zone_generation_item_add(wmOperatorType *ot)
{
socket_items::ops::add_item<ForeachGeometryElementGenerationItemsAccessor>(
ot, "Add For Each Generation Item", __func__, "Add for-each generation item");
}
static void NODE_OT_foreach_geometry_element_zone_generation_item_move(wmOperatorType *ot)
{
socket_items::ops::move_active_item<ForeachGeometryElementGenerationItemsAccessor>(
ot, "Move For Each Generation Item", __func__, "Move active for-each generation item");
}
static void NODE_OT_foreach_geometry_element_zone_main_item_remove(wmOperatorType *ot)
{
socket_items::ops::remove_active_item<ForeachGeometryElementMainItemsAccessor>(
ot, "Remove For Each Main Item", __func__, "Remove active for-each main item");
}
static void NODE_OT_foreach_geometry_element_zone_main_item_add(wmOperatorType *ot)
{
socket_items::ops::add_item<ForeachGeometryElementMainItemsAccessor>(
ot, "Add For Each Main Item", __func__, "Add for-each main item");
}
static void NODE_OT_foreach_geometry_element_zone_main_item_move(wmOperatorType *ot)
{
socket_items::ops::move_active_item<ForeachGeometryElementMainItemsAccessor>(
ot, "Move For Each Main Item", __func__, "Move active for-each main item");
}
static void node_operators()
{
WM_operatortype_append(NODE_OT_foreach_geometry_element_zone_input_item_add);
WM_operatortype_append(NODE_OT_foreach_geometry_element_zone_input_item_remove);
WM_operatortype_append(NODE_OT_foreach_geometry_element_zone_input_item_move);
WM_operatortype_append(NODE_OT_foreach_geometry_element_zone_generation_item_add);
WM_operatortype_append(NODE_OT_foreach_geometry_element_zone_generation_item_remove);
WM_operatortype_append(NODE_OT_foreach_geometry_element_zone_generation_item_move);
WM_operatortype_append(NODE_OT_foreach_geometry_element_zone_main_item_add);
WM_operatortype_append(NODE_OT_foreach_geometry_element_zone_main_item_remove);
WM_operatortype_append(NODE_OT_foreach_geometry_element_zone_main_item_move);
}
static void node_extra_info(NodeExtraInfoParams &params)
{
const NodeGeometryForeachGeometryElementOutput &storage = node_storage(params.node);
if (storage.generation_items.items_num > 0) {
if (storage.generation_items.items[0].socket_type != SOCK_GEOMETRY) {
NodeExtraInfoRow row;
row.text = RPT_("Missing Geometry");
row.tooltip = TIP_("Each output field has to correspond to a geometry that is above it");
row.icon = ICON_ERROR;
params.rows.append(std::move(row));
}
}
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype,
GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT,
"For Each Geometry Element Output",
NODE_CLASS_INTERFACE);
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.labelfunc = input_node::node_label;
ntype.insert_link = node_insert_link;
ntype.draw_buttons_ex = node_layout_ex;
ntype.register_operators = node_operators;
ntype.get_extra_info = node_extra_info;
ntype.no_muting = true;
blender::bke::node_type_storage(
&ntype, "NodeGeometryForeachGeometryElementOutput", node_free_storage, node_copy_storage);
blender::bke::node_register_type(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace output_node
} // namespace blender::nodes::node_geo_foreach_geometry_element_cc
namespace blender::nodes {
StructRNA *ForeachGeometryElementInputItemsAccessor::item_srna =
&RNA_ForeachGeometryElementInputItem;
int ForeachGeometryElementInputItemsAccessor::node_type = GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT;
void ForeachGeometryElementInputItemsAccessor::blend_write(BlendWriter *writer, const bNode &node)
{
const auto &storage = *static_cast<const NodeGeometryForeachGeometryElementOutput *>(
node.storage);
BLO_write_struct_array(writer,
NodeForeachGeometryElementInputItem,
storage.input_items.items_num,
storage.input_items.items);
for (const NodeForeachGeometryElementInputItem &item :
Span(storage.input_items.items, storage.input_items.items_num))
{
BLO_write_string(writer, item.name);
}
}
void ForeachGeometryElementInputItemsAccessor::blend_read_data(BlendDataReader *reader,
bNode &node)
{
auto &storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(node.storage);
BLO_read_struct_array(reader,
NodeForeachGeometryElementInputItem,
storage.input_items.items_num,
&storage.input_items.items);
for (const NodeForeachGeometryElementInputItem &item :
Span(storage.input_items.items, storage.input_items.items_num))
{
BLO_read_string(reader, &item.name);
}
}
StructRNA *ForeachGeometryElementMainItemsAccessor::item_srna =
&RNA_ForeachGeometryElementMainItem;
int ForeachGeometryElementMainItemsAccessor::node_type = GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT;
void ForeachGeometryElementMainItemsAccessor::blend_write(BlendWriter *writer, const bNode &node)
{
const auto &storage = *static_cast<const NodeGeometryForeachGeometryElementOutput *>(
node.storage);
BLO_write_struct_array(writer,
NodeForeachGeometryElementMainItem,
storage.main_items.items_num,
storage.main_items.items);
for (const NodeForeachGeometryElementMainItem &item :
Span(storage.main_items.items, storage.main_items.items_num))
{
BLO_write_string(writer, item.name);
}
}
void ForeachGeometryElementMainItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node)
{
auto &storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(node.storage);
BLO_read_struct_array(reader,
NodeForeachGeometryElementMainItem,
storage.main_items.items_num,
&storage.main_items.items);
for (const NodeForeachGeometryElementMainItem &item :
Span(storage.main_items.items, storage.main_items.items_num))
{
BLO_read_string(reader, &item.name);
}
}
StructRNA *ForeachGeometryElementGenerationItemsAccessor::item_srna =
&RNA_ForeachGeometryElementGenerationItem;
int ForeachGeometryElementGenerationItemsAccessor::node_type =
GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT;
void ForeachGeometryElementGenerationItemsAccessor::blend_write(BlendWriter *writer,
const bNode &node)
{
const auto &storage = *static_cast<const NodeGeometryForeachGeometryElementOutput *>(
node.storage);
BLO_write_struct_array(writer,
NodeForeachGeometryElementGenerationItem,
storage.generation_items.items_num,
storage.generation_items.items);
for (const NodeForeachGeometryElementGenerationItem &item :
Span(storage.generation_items.items, storage.generation_items.items_num))
{
BLO_write_string(writer, item.name);
}
}
void ForeachGeometryElementGenerationItemsAccessor::blend_read_data(BlendDataReader *reader,
bNode &node)
{
auto &storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(node.storage);
BLO_read_struct_array(reader,
NodeForeachGeometryElementGenerationItem,
storage.generation_items.items_num,
&storage.generation_items.items);
for (const NodeForeachGeometryElementGenerationItem &item :
Span(storage.generation_items.items, storage.generation_items.items_num))
{
BLO_read_string(reader, &item.name);
}
}
} // namespace blender::nodes

View File

@@ -39,8 +39,10 @@
#include "BKE_anonymous_attribute_make.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_nodes_gizmos_transforms.hh"
#include "BKE_geometry_set.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_node_socket_value.hh"
#include "BKE_node_tree_anonymous_attributes.hh"
#include "BKE_node_tree_zones.hh"
@@ -51,6 +53,9 @@
#include "DEG_depsgraph_query.hh"
#include "GEO_extract_elements.hh"
#include "GEO_join_geometries.hh"
#include <fmt/format.h>
#include <sstream>
@@ -1593,13 +1598,28 @@ struct ZoneBodyFunction {
ZoneFunctionIndices indices;
};
static bool ignore_zone_bsocket(const bNodeSocket &bsocket)
{
if (!bsocket.is_available()) {
return true;
}
if (!bsocket.typeinfo->geometry_nodes_cpp_type) {
/* These are typically extend sockets. */
return true;
}
return false;
}
static void initialize_zone_wrapper(const bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn,
Vector<lf::Input> &r_inputs,
Vector<lf::Output> &r_outputs)
{
for (const bNodeSocket *socket : zone.input_node->input_sockets().drop_back(1)) {
for (const bNodeSocket *socket : zone.input_node->input_sockets()) {
if (ignore_zone_bsocket(*socket)) {
continue;
}
zone_info.indices.inputs.main.append(r_inputs.append_and_get_index_as(
socket->name, *socket->typeinfo->geometry_nodes_cpp_type, lf::ValueUsage::Maybe));
}
@@ -1611,15 +1631,21 @@ static void initialize_zone_wrapper(const bNodeTreeZone &zone,
lf::ValueUsage::Maybe));
}
for (const bNodeSocket *socket : zone.output_node->output_sockets().drop_back(1)) {
for (const bNodeSocket *socket : zone.output_node->output_sockets()) {
if (ignore_zone_bsocket(*socket)) {
continue;
}
const CPPType *cpp_type = socket->typeinfo->geometry_nodes_cpp_type;
zone_info.indices.inputs.output_usages.append(
r_inputs.append_and_get_index_as("Usage", CPPType::get<bool>(), lf::ValueUsage::Maybe));
zone_info.indices.outputs.main.append(r_outputs.append_and_get_index_as(
socket->name, *socket->typeinfo->geometry_nodes_cpp_type));
zone_info.indices.outputs.main.append(
r_outputs.append_and_get_index_as(socket->name, *cpp_type));
}
for ([[maybe_unused]] const bNodeSocket *socket : zone.input_node->input_sockets().drop_back(1))
{
for ([[maybe_unused]] const bNodeSocket *socket : zone.input_node->input_sockets()) {
if (ignore_zone_bsocket(*socket)) {
continue;
}
zone_info.indices.outputs.input_usages.append(
r_outputs.append_and_get_index_as("Usage", CPPType::get<bool>()));
}
@@ -1646,27 +1672,43 @@ static void initialize_zone_wrapper(const bNodeTreeZone &zone,
static std::string zone_wrapper_input_name(const ZoneBuildInfo &zone_info,
const bNodeTreeZone &zone,
const Span<lf::Input> inputs,
const int i)
const int lf_socket_i)
{
if (zone_info.indices.inputs.output_usages.contains(i)) {
const bNodeSocket &bsocket = zone.output_node->output_socket(
i - zone_info.indices.inputs.output_usages.first());
return "Usage: " + StringRef(bsocket.name);
if (zone_info.indices.inputs.output_usages.contains(lf_socket_i)) {
const int output_usage_i = lf_socket_i - zone_info.indices.inputs.output_usages.first();
int current_valid_i = 0;
for (const bNodeSocket *bsocket : zone.output_node->output_sockets()) {
if (ignore_zone_bsocket(*bsocket)) {
continue;
}
if (current_valid_i == output_usage_i) {
return "Usage: " + StringRef(bsocket->name);
}
current_valid_i++;
}
}
return inputs[i].debug_name;
return inputs[lf_socket_i].debug_name;
}
static std::string zone_wrapper_output_name(const ZoneBuildInfo &zone_info,
const bNodeTreeZone &zone,
const Span<lf::Output> outputs,
const int i)
const int lf_socket_i)
{
if (zone_info.indices.outputs.input_usages.contains(i)) {
const bNodeSocket &bsocket = zone.input_node->input_socket(
i - zone_info.indices.outputs.input_usages.first());
return "Usage: " + StringRef(bsocket.name);
if (zone_info.indices.outputs.input_usages.contains(lf_socket_i)) {
const int input_usage_i = lf_socket_i - zone_info.indices.outputs.input_usages.first();
int current_valid_i = 0;
for (const bNodeSocket *bsocket : zone.input_node->input_sockets()) {
if (ignore_zone_bsocket(*bsocket)) {
continue;
}
if (current_valid_i == input_usage_i) {
return "Usage: " + StringRef(bsocket->name);
}
current_valid_i++;
}
}
return outputs[i].debug_name;
return outputs[lf_socket_i].debug_name;
}
/**
@@ -1727,7 +1769,7 @@ class RepeatZoneSideEffectProvider : public lf::GraphExecutorSideEffectProvider
}
const ComputeContextHash &context_hash = user_data.compute_context->hash();
const Span<int> iterations_with_side_effects =
call_data.side_effect_nodes->iterations_by_repeat_zone.lookup(
call_data.side_effect_nodes->iterations_by_iteration_zone.lookup(
{context_hash, repeat_output_bnode_->identifier});
Vector<const lf::FunctionNode *> lf_nodes;
@@ -2044,6 +2086,1237 @@ class LazyFunctionForRepeatZone : public LazyFunction {
}
};
class LazyFunctionForForeachGeometryElementZone;
struct ForeachGeometryElementEvalStorage;
struct ForeachElementComponentID {
GeometryComponent::Type component_type;
AttrDomain domain;
std::optional<int> layer_index;
};
/**
* The For Each Geometry Element can iterate over multiple components at the same time. That can
* happen when the input geometry is e.g. a mesh and a pointcloud and we're iterating over points.
*
* This struct contains evaluation data for each component.
*/
struct ForeachElementComponent {
ForeachElementComponentID id;
/** Used for field evaluation on the output node. */
std::optional<bke::GeometryFieldContext> field_context;
std::optional<FieldEvaluator> field_evaluator;
/** Index values passed into each body node. */
Array<SocketValueVariant> index_values;
/** Evaluated input values passed into each body node. */
Array<Array<SocketValueVariant>> item_input_values;
/** Geometry for each iteration. */
std::optional<Array<GeometrySet>> element_geometries;
/** The set of body evaluation nodes that correspond to this component. This indexes into
* `lf_body_nodes`. */
IndexRange body_nodes_range;
void emplace_field_context(const GeometrySet &geometry)
{
if (this->id.component_type == GeometryComponent::Type::GreasePencil &&
ELEM(this->id.domain, AttrDomain::Point, AttrDomain::Curve))
{
this->field_context.emplace(
*geometry.get_grease_pencil(), this->id.domain, *this->id.layer_index);
}
else {
this->field_context.emplace(*geometry.get_component(this->id.component_type),
this->id.domain);
}
}
AttributeAccessor input_attributes() const
{
return *this->field_context->attributes();
}
MutableAttributeAccessor attributes_for_write(GeometrySet &geometry) const
{
if (this->id.component_type == GeometryComponent::Type::GreasePencil &&
ELEM(this->id.domain, AttrDomain::Point, AttrDomain::Curve))
{
BLI_assert(this->id.layer_index.has_value());
GreasePencil *grease_pencil = geometry.get_grease_pencil_for_write();
const bke::greasepencil::Layer &layer = grease_pencil->layer(*this->id.layer_index);
bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(layer);
BLI_assert(drawing);
return drawing->strokes_for_write().attributes_for_write();
}
GeometryComponent &component = geometry.get_component_for_write(this->id.component_type);
return *component.attributes_for_write();
}
};
/**
* A lazy-function that takes the result from all loop body evaluations and reduces them to the
* final output of the entire zone.
*/
struct LazyFunctionForReduceForeachGeometryElement : public LazyFunction {
const LazyFunctionForForeachGeometryElementZone &parent_;
ForeachGeometryElementEvalStorage &eval_storage_;
public:
LazyFunctionForReduceForeachGeometryElement(
const LazyFunctionForForeachGeometryElementZone &parent,
ForeachGeometryElementEvalStorage &eval_storage);
void execute_impl(lf::Params &params, const lf::Context &context) const override;
void handle_main_items_and_geometry(lf::Params &params, const lf::Context &context) const;
void handle_generation_items(lf::Params &params, const lf::Context &context) const;
int handle_invalid_generation_items(lf::Params &params) const;
void handle_generation_item_groups(lf::Params &params,
const lf::Context &context,
int first_valid_item_i) const;
void handle_generation_items_group(lf::Params &params,
const lf::Context &context,
int geometry_item_i,
IndexRange generation_items_range) const;
bool handle_generation_items_group_lazyness(lf::Params &params,
const lf::Context &context,
int geometry_item_i,
IndexRange generation_items_range) const;
};
/**
* This is called whenever an evaluation node is entered. It sets up the compute context if the
* node is a loop body node.
*/
class ForeachGeometryElementNodeExecuteWrapper : public lf::GraphExecutorNodeExecuteWrapper {
public:
const bNode *output_bnode_ = nullptr;
VectorSet<lf::FunctionNode *> *lf_body_nodes_ = nullptr;
void execute_node(const lf::FunctionNode &node,
lf::Params &params,
const lf::Context &context) const
{
GeoNodesLFUserData &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
const int index = lf_body_nodes_->index_of_try(const_cast<lf::FunctionNode *>(&node));
const LazyFunction &fn = node.function();
if (index == -1) {
/* The node is not a loop body node, just execute it normally. */
fn.execute(params, context);
return;
}
/* Setup context for the loop body evaluation. */
bke::ForeachGeometryElementZoneComputeContext body_compute_context{
user_data.compute_context, *output_bnode_, index};
GeoNodesLFUserData body_user_data = user_data;
body_user_data.compute_context = &body_compute_context;
body_user_data.log_socket_values = should_log_socket_values_for_context(
user_data, body_compute_context.hash());
GeoNodesLFLocalUserData body_local_user_data{body_user_data};
lf::Context body_context{context.storage, &body_user_data, &body_local_user_data};
fn.execute(params, body_context);
}
};
/**
* Tells the lazy-function graph executor which loop bodies should be evaluated even if they are
* not requested by the output.
*/
class ForeachGeometryElementZoneSideEffectProvider : public lf::GraphExecutorSideEffectProvider {
public:
const bNode *output_bnode_ = nullptr;
Span<lf::FunctionNode *> lf_body_nodes_;
Vector<const lf::FunctionNode *> get_nodes_with_side_effects(
const lf::Context &context) const override
{
GeoNodesLFUserData &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
const GeoNodesCallData &call_data = *user_data.call_data;
if (!call_data.side_effect_nodes) {
return {};
}
const ComputeContextHash &context_hash = user_data.compute_context->hash();
const Span<int> iterations_with_side_effects =
call_data.side_effect_nodes->iterations_by_iteration_zone.lookup(
{context_hash, output_bnode_->identifier});
Vector<const lf::FunctionNode *> lf_nodes;
for (const int i : iterations_with_side_effects) {
if (i >= 0 && i < lf_body_nodes_.size()) {
lf_nodes.append(lf_body_nodes_[i]);
}
}
return lf_nodes;
}
};
/**
* This is only evaluated when the zone is actually evaluated. It contains all the temporary data
* that is needed for that specific evaluation.
*/
struct ForeachGeometryElementEvalStorage {
LinearAllocator<> allocator;
/** The lazy-function graph and its executor. */
lf::Graph graph;
std::optional<ForeachGeometryElementZoneSideEffectProvider> side_effect_provider;
std::optional<ForeachGeometryElementNodeExecuteWrapper> body_execute_wrapper;
std::optional<lf::GraphExecutor> graph_executor;
void *graph_executor_storage = nullptr;
/** Some lazy-functions that are constructed once the total number of iterations is known. */
std::optional<LazyFunctionForLogicalOr> or_function;
std::optional<LazyFunctionForReduceForeachGeometryElement> reduce_function;
/**
* All the body nodes in the lazy-function graph in order. This only contains nodes for the
* selected indices.
*/
VectorSet<lf::FunctionNode *> lf_body_nodes;
/** The main input geometry that is iterated over. */
GeometrySet main_geometry;
/** Data for each geometry component that is iterated over. */
Array<ForeachElementComponent> components;
/** Amount of iterations across all components. */
int total_iterations_num = 0;
};
class LazyFunctionForForeachGeometryElementZone : public LazyFunction {
private:
const bNodeTree &btree_;
const bNodeTreeZone &zone_;
const bNode &output_bnode_;
const ZoneBuildInfo &zone_info_;
const ZoneBodyFunction &body_fn_;
struct ItemIndices {
/* `outer` refers to sockets on the outside of the zone, and `inner` to the sockets on the
* inside. The `lf` and `bsocket` indices are similar, but the `lf` indices skip unavailable
* and extend sockets. */
IndexRange lf_outer;
IndexRange lf_inner;
IndexRange bsocket_outer;
IndexRange bsocket_inner;
};
/** Reduces the hardcoding of index offsets in lots of places below which is quite brittle. */
struct {
ItemIndices inputs;
ItemIndices main;
ItemIndices generation;
} indices_;
friend LazyFunctionForReduceForeachGeometryElement;
public:
LazyFunctionForForeachGeometryElementZone(const bNodeTree &btree,
const bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn)
: btree_(btree),
zone_(zone),
output_bnode_(*zone.output_node),
zone_info_(zone_info),
body_fn_(body_fn)
{
debug_name_ = "Foreach Geometry Element";
initialize_zone_wrapper(zone, zone_info, body_fn, inputs_, outputs_);
/* All main inputs are always used for now. */
for (const int i : zone_info.indices.inputs.main) {
inputs_[i].usage = lf::ValueUsage::Used;
}
const auto &node_storage = *static_cast<const NodeGeometryForeachGeometryElementOutput *>(
output_bnode_.storage);
const AttrDomain iteration_domain = AttrDomain(node_storage.domain);
BLI_assert(zone_.input_node->output_socket(1).is_available() ==
(iteration_domain != AttrDomain::Corner));
const int input_items_num = node_storage.input_items.items_num;
const int main_items_num = node_storage.main_items.items_num;
const int generation_items_num = node_storage.generation_items.items_num;
indices_.inputs.lf_outer = IndexRange::from_begin_size(2, input_items_num);
indices_.inputs.lf_inner = IndexRange::from_begin_size(
iteration_domain == AttrDomain::Corner ? 1 : 2, input_items_num);
indices_.inputs.bsocket_outer = indices_.inputs.lf_outer;
indices_.inputs.bsocket_inner = indices_.inputs.lf_inner;
indices_.main.lf_outer = IndexRange::from_begin_size(1, main_items_num);
indices_.main.lf_inner = IndexRange::from_begin_size(0, main_items_num);
indices_.main.bsocket_outer = indices_.main.lf_outer;
indices_.main.bsocket_inner = indices_.main.lf_inner;
indices_.generation.lf_outer = IndexRange::from_begin_size(1 + main_items_num,
generation_items_num);
indices_.generation.lf_inner = IndexRange::from_begin_size(main_items_num,
generation_items_num);
indices_.generation.bsocket_outer = IndexRange::from_begin_size(2 + main_items_num,
generation_items_num);
indices_.generation.bsocket_inner = IndexRange::from_begin_size(1 + main_items_num,
generation_items_num);
}
void *init_storage(LinearAllocator<> &allocator) const override
{
return allocator.construct<ForeachGeometryElementEvalStorage>().release();
}
void destruct_storage(void *storage) const override
{
auto *s = static_cast<ForeachGeometryElementEvalStorage *>(storage);
if (s->graph_executor_storage) {
s->graph_executor->destruct_storage(s->graph_executor_storage);
}
std::destroy_at(s);
}
void execute_impl(lf::Params &params, const lf::Context &context) const override
{
auto &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
auto &local_user_data = *static_cast<GeoNodesLFLocalUserData *>(context.local_user_data);
const auto &node_storage = *static_cast<const NodeGeometryForeachGeometryElementOutput *>(
output_bnode_.storage);
auto &eval_storage = *static_cast<ForeachGeometryElementEvalStorage *>(context.storage);
geo_eval_log::GeoTreeLogger *tree_logger = local_user_data.try_get_tree_logger(user_data);
/* Measure execution time of the entire zone. */
const geo_eval_log::TimePoint start_time = geo_eval_log::Clock::now();
BLI_SCOPED_DEFER([&]() {
if (tree_logger) {
const geo_eval_log::TimePoint end_time = geo_eval_log::Clock::now();
tree_logger->node_execution_times.append(*tree_logger->allocator,
{output_bnode_.identifier, start_time, end_time});
}
});
if (!eval_storage.graph_executor) {
/* Create the execution graph in the first evaluation. */
this->initialize_execution_graph(params, eval_storage, node_storage);
if (tree_logger) {
if (eval_storage.total_iterations_num == 0) {
if (!eval_storage.main_geometry.is_empty()) {
tree_logger->node_warnings.append(
*tree_logger->allocator,
{zone_.input_node->identifier,
{NodeWarningType::Info,
N_("Input geometry has no elements in the iteration domain.")}});
}
}
}
}
lf::Context eval_graph_context{
eval_storage.graph_executor_storage, context.user_data, context.local_user_data};
eval_storage.graph_executor->execute(params, eval_graph_context);
}
void initialize_execution_graph(
lf::Params &params,
ForeachGeometryElementEvalStorage &eval_storage,
const NodeGeometryForeachGeometryElementOutput &node_storage) const
{
eval_storage.main_geometry = params.extract_input<GeometrySet>(
zone_info_.indices.inputs.main[0]);
/* Find all the things we need to iterate over in the input geometry. */
this->prepare_components(params, eval_storage, node_storage);
/* Add interface sockets for the zone graph. Those are the same as for the entire zone, even
* though some of the inputs are not strictly needed anymore. It's easier to avoid another
* level of index remapping though. */
lf::Graph &lf_graph = eval_storage.graph;
Vector<lf::GraphInputSocket *> graph_inputs;
Vector<lf::GraphOutputSocket *> graph_outputs;
for (const int i : inputs_.index_range()) {
const lf::Input &input = inputs_[i];
graph_inputs.append(&lf_graph.add_input(*input.type, this->input_name(i)));
}
for (const int i : outputs_.index_range()) {
const lf::Output &output = outputs_[i];
graph_outputs.append(&lf_graph.add_output(*output.type, this->output_name(i)));
}
/* Add all the nodes and links to the graph. */
this->build_graph_contents(eval_storage, node_storage, graph_inputs, graph_outputs);
eval_storage.side_effect_provider.emplace();
eval_storage.side_effect_provider->output_bnode_ = &output_bnode_;
eval_storage.side_effect_provider->lf_body_nodes_ = eval_storage.lf_body_nodes;
eval_storage.body_execute_wrapper.emplace();
eval_storage.body_execute_wrapper->output_bnode_ = &output_bnode_;
eval_storage.body_execute_wrapper->lf_body_nodes_ = &eval_storage.lf_body_nodes;
lf_graph.update_node_indices();
eval_storage.graph_executor.emplace(lf_graph,
graph_inputs.as_span(),
graph_outputs.as_span(),
nullptr,
&*eval_storage.side_effect_provider,
&*eval_storage.body_execute_wrapper);
eval_storage.graph_executor_storage = eval_storage.graph_executor->init_storage(
eval_storage.allocator);
/* Log graph for debugging purposes. */
bNodeTree &btree_orig = *reinterpret_cast<bNodeTree *>(
DEG_get_original_id(const_cast<ID *>(&btree_.id)));
if (btree_orig.runtime->logged_zone_graphs) {
std::lock_guard lock{btree_orig.runtime->logged_zone_graphs->mutex};
btree_orig.runtime->logged_zone_graphs->graph_by_zone_id.lookup_or_add_cb(
output_bnode_.identifier, [&]() { return lf_graph.to_dot(); });
}
}
void prepare_components(lf::Params &params,
ForeachGeometryElementEvalStorage &eval_storage,
const NodeGeometryForeachGeometryElementOutput &node_storage) const
{
const AttrDomain iteration_domain = AttrDomain(node_storage.domain);
/* TODO: Get propagation info from input, but that's not necessary for correctness for now. */
AttributeFilter attribute_filter;
const bNodeSocket &element_geometry_bsocket = zone_.input_node->output_socket(1);
const bool create_element_geometries = element_geometry_bsocket.is_available() &&
element_geometry_bsocket.is_directly_linked();
/* Gather components to process. */
Vector<ForeachElementComponentID> component_ids;
for (const GeometryComponent *src_component : eval_storage.main_geometry.get_components()) {
const GeometryComponent::Type component_type = src_component->type();
if (src_component->type() == GeometryComponent::Type::GreasePencil &&
ELEM(iteration_domain, AttrDomain::Point, AttrDomain::Curve))
{
const GreasePencil &grease_pencil = *eval_storage.main_geometry.get_grease_pencil();
for (const int layer_i : grease_pencil.layers().index_range()) {
const bke::greasepencil::Drawing *drawing = grease_pencil.get_eval_drawing(
grease_pencil.layer(layer_i));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &curves = drawing->strokes();
if (curves.curves_num() == 0) {
continue;
}
component_ids.append({component_type, iteration_domain, layer_i});
}
}
else {
const int domain_size = src_component->attribute_domain_size(iteration_domain);
if (domain_size > 0) {
component_ids.append({component_type, iteration_domain});
}
}
}
const Field<bool> selection_field = params
.extract_input<SocketValueVariant>(
zone_info_.indices.inputs.main[1])
.extract<Field<bool>>();
/* Evaluate the selection and field inputs for all components. */
int body_nodes_offset = 0;
eval_storage.components.reinitialize(component_ids.size());
for (const int component_i : component_ids.index_range()) {
const ForeachElementComponentID id = component_ids[component_i];
ForeachElementComponent &component_info = eval_storage.components[component_i];
component_info.id = id;
component_info.emplace_field_context(eval_storage.main_geometry);
const int domain_size = component_info.input_attributes().domain_size(id.domain);
BLI_assert(domain_size > 0);
/* Prepare field evaluation for the zone inputs. */
component_info.field_evaluator.emplace(*component_info.field_context, domain_size);
component_info.field_evaluator->set_selection(selection_field);
for (const int item_i : IndexRange(node_storage.input_items.items_num)) {
const GField item_field =
params
.get_input<SocketValueVariant>(
zone_info_.indices.inputs.main[indices_.inputs.lf_outer[item_i]])
.get<GField>();
component_info.field_evaluator->add(item_field);
}
/* Evaluate all fields passed to the zone input. */
component_info.field_evaluator->evaluate();
/* The mask contains all the indices that should be iterated over in the component. */
const IndexMask mask = component_info.field_evaluator->get_evaluated_selection_as_mask();
component_info.body_nodes_range = IndexRange::from_begin_size(body_nodes_offset,
mask.size());
body_nodes_offset += mask.size();
/* Prepare indices that are passed into each iteration. */
component_info.index_values.reinitialize(mask.size());
mask.foreach_index(
[&](const int i, const int pos) { component_info.index_values[pos].set(i); });
if (create_element_geometries) {
component_info.element_geometries = this->try_extract_element_geometries(
eval_storage.main_geometry, id, mask, attribute_filter);
}
/* Prepare remaining inputs that come from the field evaluation.*/
component_info.item_input_values.reinitialize(node_storage.input_items.items_num);
for (const int item_i : IndexRange(node_storage.input_items.items_num)) {
const NodeForeachGeometryElementInputItem &item = node_storage.input_items.items[item_i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
component_info.item_input_values[item_i].reinitialize(mask.size());
const GVArray &values = component_info.field_evaluator->get_evaluated(item_i);
mask.foreach_index(GrainSize(1024), [&](const int i, const int pos) {
SocketValueVariant &value_variant = component_info.item_input_values[item_i][pos];
void *buffer = value_variant.allocate_single(socket_type);
values.get_to_uninitialized(i, buffer);
});
}
}
eval_storage.total_iterations_num = body_nodes_offset;
}
std::optional<Array<GeometrySet>> try_extract_element_geometries(
const GeometrySet &main_geometry,
const ForeachElementComponentID &id,
const IndexMask &mask,
const AttributeFilter &attribute_filter) const
{
switch (id.component_type) {
case GeometryComponent::Type::Mesh: {
const Mesh &main_mesh = *main_geometry.get_mesh();
Array<Mesh *> meshes;
switch (id.domain) {
case AttrDomain::Point: {
meshes = geometry::extract_mesh_vertices(main_mesh, mask, attribute_filter);
break;
}
case AttrDomain::Edge: {
meshes = geometry::extract_mesh_edges(main_mesh, mask, attribute_filter);
break;
}
case AttrDomain::Face: {
meshes = geometry::extract_mesh_faces(main_mesh, mask, attribute_filter);
break;
}
default: {
return std::nullopt;
}
}
Array<GeometrySet> element_geometries(meshes.size());
for (const int i : meshes.index_range()) {
element_geometries[i].replace_mesh(meshes[i]);
}
return element_geometries;
}
case GeometryComponent::Type::PointCloud: {
if (id.domain != AttrDomain::Point) {
return std::nullopt;
}
const PointCloud &main_pointcloud = *main_geometry.get_pointcloud();
Array<PointCloud *> pointclouds = geometry::extract_pointcloud_points(
main_pointcloud, mask, attribute_filter);
Array<GeometrySet> element_geometries(pointclouds.size());
for (const int i : pointclouds.index_range()) {
element_geometries[i].replace_pointcloud(pointclouds[i]);
}
return element_geometries;
}
case GeometryComponent::Type::Curve: {
const Curves &main_curves = *main_geometry.get_curves();
Array<Curves *> element_curves;
switch (id.domain) {
case AttrDomain::Point: {
element_curves = geometry::extract_curves_points(main_curves, mask, attribute_filter);
break;
}
case AttrDomain::Curve: {
element_curves = geometry::extract_curves(main_curves, mask, attribute_filter);
break;
}
default:
return std::nullopt;
}
Array<GeometrySet> element_geometries(element_curves.size());
for (const int i : element_curves.index_range()) {
element_geometries[i].replace_curves(element_curves[i]);
}
return element_geometries;
}
case GeometryComponent::Type::Instance: {
if (id.domain != AttrDomain::Instance) {
return std::nullopt;
}
const bke::Instances &main_instances = *main_geometry.get_instances();
Array<bke::Instances *> element_instances = geometry::extract_instances(
main_instances, mask, attribute_filter);
Array<GeometrySet> element_geometries(element_instances.size());
for (const int i : element_instances.index_range()) {
element_geometries[i].replace_instances(element_instances[i]);
}
return element_geometries;
}
case GeometryComponent::Type::GreasePencil: {
const GreasePencil &main_grease_pencil = *main_geometry.get_grease_pencil();
Array<GreasePencil *> element_grease_pencils;
switch (id.domain) {
case AttrDomain::Layer: {
element_grease_pencils = geometry::extract_greasepencil_layers(
main_grease_pencil, mask, attribute_filter);
break;
}
case AttrDomain::Point: {
element_grease_pencils = geometry::extract_greasepencil_layer_points(
main_grease_pencil, *id.layer_index, mask, attribute_filter);
break;
}
case AttrDomain::Curve: {
element_grease_pencils = geometry::extract_greasepencil_layer_curves(
main_grease_pencil, *id.layer_index, mask, attribute_filter);
break;
}
default:
return std::nullopt;
}
Array<GeometrySet> element_geometries(element_grease_pencils.size());
for (const int i : element_geometries.index_range()) {
element_geometries[i].replace_grease_pencil(element_grease_pencils[i]);
}
return element_geometries;
}
default:
break;
}
return std::nullopt;
}
void build_graph_contents(ForeachGeometryElementEvalStorage &eval_storage,
const NodeGeometryForeachGeometryElementOutput &node_storage,
Span<lf::GraphInputSocket *> graph_inputs,
Span<lf::GraphOutputSocket *> graph_outputs) const
{
lf::Graph &lf_graph = eval_storage.graph;
/* Create body nodes. */
VectorSet<lf::FunctionNode *> &lf_body_nodes = eval_storage.lf_body_nodes;
for ([[maybe_unused]] const int i : IndexRange(eval_storage.total_iterations_num)) {
lf::FunctionNode &lf_node = lf_graph.add_function(*body_fn_.function);
lf_body_nodes.add_new(&lf_node);
}
/* Link up output usages to body nodes. */
for (const int zone_output_i : body_fn_.indices.inputs.output_usages.index_range()) {
/* +1 because of geometry output. */
lf::GraphInputSocket &lf_graph_input =
*graph_inputs[zone_info_.indices.inputs.output_usages[1 + zone_output_i]];
for (const int i : lf_body_nodes.index_range()) {
lf::FunctionNode &lf_node = *lf_body_nodes[i];
lf_graph.add_link(lf_graph_input,
lf_node.input(body_fn_.indices.inputs.output_usages[zone_output_i]));
}
}
const bNodeSocket &element_geometry_bsocket = zone_.input_node->output_socket(1);
static const GeometrySet empty_geometry;
for (const ForeachElementComponent &component_info : eval_storage.components) {
for (const int i : component_info.body_nodes_range.index_range()) {
const int body_i = component_info.body_nodes_range[i];
lf::FunctionNode &lf_body_node = *lf_body_nodes[body_i];
/* Set index input for loop body. */
lf_body_node.input(body_fn_.indices.inputs.main[0])
.set_default_value(&component_info.index_values[i]);
/* Set geometry element input for loop body. */
if (element_geometry_bsocket.is_available()) {
const GeometrySet *element_geometry = component_info.element_geometries.has_value() ?
&(*component_info.element_geometries)[i] :
&empty_geometry;
lf_body_node.input(body_fn_.indices.inputs.main[1]).set_default_value(element_geometry);
}
/* Set main input values for loop body. */
for (const int item_i : IndexRange(node_storage.input_items.items_num)) {
lf_body_node.input(body_fn_.indices.inputs.main[indices_.inputs.lf_inner[item_i]])
.set_default_value(&component_info.item_input_values[item_i][i]);
}
/* Link up border-link inputs to the loop body. */
for (const int border_link_i : zone_info_.indices.inputs.border_links.index_range()) {
lf_graph.add_link(
*graph_inputs[zone_info_.indices.inputs.border_links[border_link_i]],
lf_body_node.input(body_fn_.indices.inputs.border_links[border_link_i]));
}
/* Link up attribute propagation information. */
for (const auto item : body_fn_.indices.inputs.attributes_by_field_source_index.items()) {
lf_graph.add_link(
*graph_inputs[zone_info_.indices.inputs.attributes_by_field_source_index.lookup(
item.key)],
lf_body_node.input(item.value));
}
for (const auto item :
body_fn_.indices.inputs.attributes_by_caller_propagation_index.items())
{
lf_graph.add_link(
*graph_inputs[zone_info_.indices.inputs.attributes_by_caller_propagation_index
.lookup(item.key)],
lf_body_node.input(item.value));
}
}
}
/* Add the reduce function that has all outputs from the zone bodies as input. */
eval_storage.reduce_function.emplace(*this, eval_storage);
lf::FunctionNode &lf_reduce = lf_graph.add_function(*eval_storage.reduce_function);
/* Link up body outputs to reduce function. */
const int body_main_outputs_num = node_storage.main_items.items_num +
node_storage.generation_items.items_num;
BLI_assert(body_main_outputs_num == body_fn_.indices.outputs.main.size());
for (const int i : IndexRange(eval_storage.total_iterations_num)) {
lf::FunctionNode &lf_body_node = *lf_body_nodes[i];
for (const int item_i : IndexRange(node_storage.main_items.items_num)) {
lf_graph.add_link(lf_body_node.output(body_fn_.indices.outputs.main[item_i]),
lf_reduce.input(i * body_main_outputs_num + item_i));
}
for (const int item_i : IndexRange(node_storage.generation_items.items_num)) {
const int body_output_i = item_i + node_storage.main_items.items_num;
lf_graph.add_link(lf_body_node.output(body_fn_.indices.outputs.main[body_output_i]),
lf_reduce.input(i * body_main_outputs_num + body_output_i));
}
}
/* Link up reduce function outputs to final zone outputs. */
lf_graph.add_link(lf_reduce.output(0), *graph_outputs[zone_info_.indices.outputs.main[0]]);
for (const int item_i : IndexRange(node_storage.main_items.items_num)) {
const int output_i = indices_.main.lf_outer[item_i];
lf_graph.add_link(lf_reduce.output(output_i),
*graph_outputs[zone_info_.indices.outputs.main[output_i]]);
}
for (const int item_i : IndexRange(node_storage.generation_items.items_num)) {
const int output_i = indices_.generation.lf_outer[item_i];
lf_graph.add_link(lf_reduce.output(output_i),
*graph_outputs[zone_info_.indices.outputs.main[output_i]]);
}
/* All zone inputs are used for now. */
static bool static_true{true};
for (const int i : zone_info_.indices.outputs.input_usages) {
graph_outputs[i]->set_default_value(&static_true);
}
/* Handle usage outputs for border-links. A border-link is used if it's used by any of the
* iterations. */
eval_storage.or_function.emplace(eval_storage.total_iterations_num);
for (const int border_link_i : zone_.border_links.index_range()) {
lf::FunctionNode &lf_or = lf_graph.add_function(*eval_storage.or_function);
for (const int i : lf_body_nodes.index_range()) {
lf::FunctionNode &lf_body_node = *lf_body_nodes[i];
lf_graph.add_link(
lf_body_node.output(body_fn_.indices.outputs.border_link_usages[border_link_i]),
lf_or.input(i));
}
lf_graph.add_link(
lf_or.output(0),
*graph_outputs[zone_info_.indices.outputs.border_link_usages[border_link_i]]);
}
}
std::string input_name(const int i) const override
{
return zone_wrapper_input_name(zone_info_, zone_, inputs_, i);
}
std::string output_name(const int i) const override
{
return zone_wrapper_output_name(zone_info_, zone_, outputs_, i);
}
};
LazyFunctionForReduceForeachGeometryElement::LazyFunctionForReduceForeachGeometryElement(
const LazyFunctionForForeachGeometryElementZone &parent,
ForeachGeometryElementEvalStorage &eval_storage)
: parent_(parent), eval_storage_(eval_storage)
{
debug_name_ = "Reduce";
const auto &node_storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(
parent.output_bnode_.storage);
inputs_.reserve(eval_storage.total_iterations_num *
(node_storage.main_items.items_num + node_storage.generation_items.items_num));
for ([[maybe_unused]] const int i : eval_storage.lf_body_nodes.index_range()) {
/* Add parameters for main items. */
for (const int item_i : IndexRange(node_storage.main_items.items_num)) {
const NodeForeachGeometryElementMainItem &item = node_storage.main_items.items[item_i];
const bNodeSocket &socket = parent.output_bnode_.input_socket(
parent_.indices_.main.bsocket_inner[item_i]);
inputs_.append_as(
item.name, *socket.typeinfo->geometry_nodes_cpp_type, lf::ValueUsage::Used);
}
/* Add parameters for generation items. */
for (const int item_i : IndexRange(node_storage.generation_items.items_num)) {
const NodeForeachGeometryElementGenerationItem &item =
node_storage.generation_items.items[item_i];
const bNodeSocket &socket = parent.output_bnode_.input_socket(
parent_.indices_.generation.bsocket_inner[item_i]);
inputs_.append_as(
item.name, *socket.typeinfo->geometry_nodes_cpp_type, lf::ValueUsage::Maybe);
}
}
/* Add output for main geometry. */
outputs_.append_as("Geometry", CPPType::get<GeometrySet>());
/* Add outputs for main items. */
for (const int item_i : IndexRange(node_storage.main_items.items_num)) {
const NodeForeachGeometryElementMainItem &item = node_storage.main_items.items[item_i];
const bNodeSocket &socket = parent.output_bnode_.output_socket(
parent_.indices_.main.bsocket_outer[item_i]);
outputs_.append_as(item.name, *socket.typeinfo->geometry_nodes_cpp_type);
}
/* Add outputs for generation items. */
for (const int item_i : IndexRange(node_storage.generation_items.items_num)) {
const NodeForeachGeometryElementGenerationItem &item =
node_storage.generation_items.items[item_i];
const bNodeSocket &socket = parent.output_bnode_.output_socket(
parent_.indices_.generation.bsocket_outer[item_i]);
outputs_.append_as(item.name, *socket.typeinfo->geometry_nodes_cpp_type);
}
}
/** Gives the domain with the smallest number of elements that always exists. */
static std::optional<AttrDomain> get_foreach_attribute_propagation_target_domain(
const GeometryComponent::Type component_type)
{
switch (component_type) {
case GeometryComponent::Type::Mesh:
case GeometryComponent::Type::PointCloud:
return AttrDomain::Point;
case GeometryComponent::Type::Curve:
return AttrDomain::Curve;
case GeometryComponent::Type::Instance:
return AttrDomain::Instance;
case GeometryComponent::Type::GreasePencil:
return AttrDomain::Layer;
default:
break;
}
return std::nullopt;
}
void LazyFunctionForReduceForeachGeometryElement::execute_impl(lf::Params &params,
const lf::Context &context) const
{
const auto &node_storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(
parent_.output_bnode_.storage);
this->handle_main_items_and_geometry(params, context);
if (node_storage.generation_items.items_num == 0) {
return;
}
this->handle_generation_items(params, context);
}
void LazyFunctionForReduceForeachGeometryElement::handle_main_items_and_geometry(
lf::Params &params, const lf::Context &context) const
{
auto &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
const auto &node_storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(
parent_.output_bnode_.storage);
const int body_main_outputs_num = node_storage.main_items.items_num +
node_storage.generation_items.items_num;
const int main_geometry_output = 0;
if (params.output_was_set(main_geometry_output)) {
/* Done already. */
return;
}
GeometrySet output_geometry = eval_storage_.main_geometry;
for (const int item_i : IndexRange(node_storage.main_items.items_num)) {
const NodeForeachGeometryElementMainItem &item = node_storage.main_items.items[item_i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const CPPType *base_cpp_type = bke::socket_type_to_geo_nodes_base_cpp_type(socket_type);
if (!base_cpp_type) {
continue;
}
const eCustomDataType cd_type = bke::cpp_type_to_custom_data_type(*base_cpp_type);
/* Compute output attribute name for this item. */
const std::string attribute_name = bke::hash_to_anonymous_attribute_name(
user_data.call_data->self_object()->id.name,
user_data.compute_context->hash(),
parent_.output_bnode_.identifier,
item.identifier);
/* Create a new output attribute for the current item on each iteration component. */
for (const ForeachElementComponent &component_info : eval_storage_.components) {
MutableAttributeAccessor attributes = component_info.attributes_for_write(output_geometry);
const int domain_size = attributes.domain_size(component_info.id.domain);
const IndexMask mask = component_info.field_evaluator->get_evaluated_selection_as_mask();
/* Actually create the attribute. */
GSpanAttributeWriter attribute = attributes.lookup_or_add_for_write_only_span(
attribute_name, component_info.id.domain, cd_type);
/* Fill the elements of the attribute that we didn't iterate over because they were not
* selected. */
IndexMaskMemory memory;
const IndexMask inverted_mask = mask.complement(IndexRange(domain_size), memory);
base_cpp_type->value_initialize_indices(attribute.span.data(), inverted_mask);
/* Copy the values from each iteration into the attribute. */
mask.foreach_index([&](const int i, const int pos) {
const int lf_param_index = pos * body_main_outputs_num + item_i;
SocketValueVariant &value_variant = params.get_input<SocketValueVariant>(lf_param_index);
value_variant.convert_to_single();
const void *value = value_variant.get_single_ptr_raw();
base_cpp_type->copy_construct(value, attribute.span[i]);
});
attribute.finish();
}
/* Output the field for the anonymous attribute. */
auto attribute_field = std::make_shared<AttributeFieldInput>(
attribute_name,
*base_cpp_type,
make_anonymous_attribute_socket_inspection_string(
parent_.output_bnode_.output_socket(parent_.indices_.main.bsocket_outer[item_i])));
SocketValueVariant attribute_value_variant{GField(std::move(attribute_field))};
params.set_output(1 + item_i, std::move(attribute_value_variant));
}
/* Output the original geometry with potentially additional attributes. */
params.set_output(main_geometry_output, std::move(output_geometry));
}
void LazyFunctionForReduceForeachGeometryElement::handle_generation_items(
lf::Params &params, const lf::Context &context) const
{
const auto &node_storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(
parent_.output_bnode_.storage);
const int first_valid_item_i = this->handle_invalid_generation_items(params);
if (first_valid_item_i == node_storage.generation_items.items_num) {
return;
}
this->handle_generation_item_groups(params, context, first_valid_item_i);
}
int LazyFunctionForReduceForeachGeometryElement::handle_invalid_generation_items(
lf::Params &params) const
{
const auto &node_storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(
parent_.output_bnode_.storage);
int item_i = 0;
/* Handle invalid generation items that come before a geometry. */
for (; item_i < node_storage.generation_items.items_num; item_i++) {
const NodeForeachGeometryElementGenerationItem &item =
node_storage.generation_items.items[item_i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
if (socket_type == SOCK_GEOMETRY) {
break;
}
const int lf_socket_i = parent_.indices_.generation.lf_outer[item_i];
if (!params.output_was_set(lf_socket_i)) {
const int bsocket_i = parent_.indices_.generation.bsocket_outer[item_i];
set_default_value_for_output_socket(
params, lf_socket_i, parent_.zone_.output_node->output_socket(bsocket_i));
}
}
return item_i;
}
void LazyFunctionForReduceForeachGeometryElement::handle_generation_item_groups(
lf::Params &params, const lf::Context &context, const int first_valid_item_i) const
{
const auto &node_storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(
parent_.output_bnode_.storage);
int previous_geometry_item_i = first_valid_item_i;
/* Iterate over all groups. A group starts with a geometry socket followed by an arbitrary number
* of non-geometry sockets. */
for (const int item_i :
IndexRange::from_begin_end(first_valid_item_i + 1, node_storage.generation_items.items_num))
{
const NodeForeachGeometryElementGenerationItem &item =
node_storage.generation_items.items[item_i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
if (socket_type == SOCK_GEOMETRY) {
this->handle_generation_items_group(
params,
context,
previous_geometry_item_i,
IndexRange::from_begin_end(previous_geometry_item_i + 1, item_i));
previous_geometry_item_i = item_i;
}
}
this->handle_generation_items_group(
params,
context,
previous_geometry_item_i,
IndexRange::from_begin_end(previous_geometry_item_i + 1,
node_storage.generation_items.items_num));
}
void LazyFunctionForReduceForeachGeometryElement::handle_generation_items_group(
lf::Params &params,
const lf::Context &context,
const int geometry_item_i,
const IndexRange generation_items_range) const
{
auto &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
const auto &node_storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(
parent_.output_bnode_.storage);
const int body_main_outputs_num = node_storage.main_items.items_num +
node_storage.generation_items.items_num;
/* Handle the case when the output is not needed or the inputs have not been computed yet. */
if (!this->handle_generation_items_group_lazyness(
params, context, geometry_item_i, generation_items_range))
{
return;
}
/* TODO: Get propagation info from input, but that's not necessary for correctness for now. */
AttributeFilter attribute_filter;
const int bodies_num = eval_storage_.lf_body_nodes.size();
Array<GeometrySet> geometries(bodies_num);
/* Create attribute names for the outputs. */
Array<std::string> attribute_names(generation_items_range.size());
for (const int i : generation_items_range.index_range()) {
const int item_i = generation_items_range[i];
const NodeForeachGeometryElementGenerationItem &item =
node_storage.generation_items.items[item_i];
attribute_names[i] = bke::hash_to_anonymous_attribute_name(
user_data.call_data->self_object()->id.name,
user_data.compute_context->hash(),
parent_.output_bnode_.identifier,
item.identifier);
}
for (const ForeachElementComponent &component_info : eval_storage_.components) {
const AttributeAccessor src_attributes = component_info.input_attributes();
/* These are the attributes we need to propagate from the original input geometry. */
struct NameWithType {
StringRef name;
eCustomDataType type;
};
Vector<NameWithType> attributes_to_propagate;
src_attributes.for_all([&](const StringRef name, const AttributeMetaData &meta_data) {
if (meta_data.data_type == CD_PROP_STRING) {
return true;
}
if (attribute_filter.allow_skip(name)) {
return true;
}
attributes_to_propagate.append({name, meta_data.data_type});
return true;
});
Map<StringRef, GVArray> cached_adapted_src_attributes;
const IndexMask mask = component_info.field_evaluator->get_evaluated_selection_as_mask();
/* Add attributes for each field on the geometry created by each iteration. */
mask.foreach_index([&](const int element_i, const int local_body_i) {
const int body_i = component_info.body_nodes_range[local_body_i];
const int geometry_param_i = body_i * body_main_outputs_num +
parent_.indices_.generation.lf_inner[geometry_item_i];
GeometrySet &geometry = geometries[body_i];
geometry = params.extract_input<GeometrySet>(geometry_param_i);
for (const GeometryComponent::Type dst_component_type :
{GeometryComponent::Type::Mesh,
GeometryComponent::Type::PointCloud,
GeometryComponent::Type::Curve,
GeometryComponent::Type::GreasePencil,
GeometryComponent::Type::Instance})
{
if (!geometry.has(dst_component_type)) {
continue;
}
GeometryComponent &dst_component = geometry.get_component_for_write(dst_component_type);
MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write();
/* Determine the domain that we propagate the input attribute to. Technically, this is only
* a single value for the entire geometry, but we can't optimize for that yet. */
const std::optional<AttrDomain> propagation_domain =
get_foreach_attribute_propagation_target_domain(dst_component_type);
if (!propagation_domain) {
continue;
}
/* Propagate attributes from the input geometry. */
for (const NameWithType &name_with_type : attributes_to_propagate) {
const StringRef name = name_with_type.name;
const eCustomDataType cd_type = name_with_type.type;
if (src_attributes.is_builtin(name) && !dst_attributes.is_builtin(name)) {
continue;
}
if (dst_attributes.contains(name)) {
/* Attributes created in the zone shouldn't be overridden. */
continue;
}
/* Get the source attribute adapted to the iteration domain. */
const GVArray &src_attribute = cached_adapted_src_attributes.lookup_or_add_cb(
name, [&]() {
GAttributeReader attribute = src_attributes.lookup(name);
return src_attributes.adapt_domain(
*attribute, attribute.domain, component_info.id.domain);
});
if (!src_attribute) {
continue;
}
const CPPType &type = src_attribute.type();
BUFFER_FOR_CPP_TYPE_VALUE(type, element_value);
src_attribute.get_to_uninitialized(element_i, element_value);
/* Actually create the attribute. */
GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span(
name, *propagation_domain, cd_type);
type.fill_assign_n(element_value, dst_attribute.span.data(), dst_attribute.span.size());
dst_attribute.finish();
type.destruct(element_value);
}
}
/* Create an attribute for each field that corresponds to the current geometry. */
for (const int local_item_i : generation_items_range.index_range()) {
const int item_i = generation_items_range[local_item_i];
const NodeForeachGeometryElementGenerationItem &item =
node_storage.generation_items.items[item_i];
const AttrDomain capture_domain = AttrDomain(item.domain);
const int field_param_i = body_i * body_main_outputs_num +
parent_.indices_.generation.lf_inner[item_i];
GField field = params.get_input<SocketValueVariant>(field_param_i).get<GField>();
if (capture_domain == AttrDomain::Instance) {
if (geometry.has_instances()) {
bke::try_capture_field_on_geometry(
geometry.get_component_for_write(GeometryComponent::Type::Instance),
attribute_names[local_item_i],
capture_domain,
field);
}
}
else {
geometry.modify_geometry_sets([&](GeometrySet &sub_geometry) {
for (const GeometryComponent::Type component_type :
{GeometryComponent::Type::Mesh,
GeometryComponent::Type::PointCloud,
GeometryComponent::Type::Curve,
GeometryComponent::Type::GreasePencil})
{
if (sub_geometry.has(component_type)) {
bke::try_capture_field_on_geometry(
sub_geometry.get_component_for_write(component_type),
attribute_names[local_item_i],
capture_domain,
field);
}
}
});
}
}
});
}
/* Join the geometries from all iterations into a single one. */
GeometrySet joined_geometry = geometry::join_geometries(geometries, attribute_filter);
/* Output the joined geometry. */
params.set_output(parent_.indices_.generation.lf_outer[geometry_item_i],
std::move(joined_geometry));
/* Output the anonymous attribute fields. */
for (const int local_item_i : generation_items_range.index_range()) {
const int item_i = generation_items_range[local_item_i];
const NodeForeachGeometryElementGenerationItem &item =
node_storage.generation_items.items[item_i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const CPPType &base_cpp_type = *bke::socket_type_to_geo_nodes_base_cpp_type(socket_type);
const StringRef attribute_name = attribute_names[local_item_i];
auto attribute_field = std::make_shared<AttributeFieldInput>(
attribute_name,
base_cpp_type,
make_anonymous_attribute_socket_inspection_string(
parent_.output_bnode_.output_socket(2 + node_storage.main_items.items_num + item_i)));
SocketValueVariant attribute_value_variant{GField(std::move(attribute_field))};
params.set_output(parent_.indices_.generation.lf_outer[item_i],
std::move(attribute_value_variant));
}
}
bool LazyFunctionForReduceForeachGeometryElement::handle_generation_items_group_lazyness(
lf::Params &params,
const lf::Context & /*context*/,
const int geometry_item_i,
const IndexRange generation_items_range) const
{
const auto &node_storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(
parent_.output_bnode_.storage);
const int body_main_outputs_num = node_storage.main_items.items_num +
node_storage.generation_items.items_num;
const int geometry_output_param = parent_.indices_.generation.lf_outer[geometry_item_i];
if (params.output_was_set(geometry_output_param)) {
/* Done already. */
return false;
}
const lf::ValueUsage geometry_output_usage = params.get_output_usage(geometry_output_param);
if (geometry_output_usage == lf::ValueUsage::Unused) {
/* Output dummy values. */
const int start_bsocket_i = parent_.indices_.generation.bsocket_outer[geometry_item_i];
for (const int i : IndexRange(1 + generation_items_range.size())) {
const bNodeSocket &bsocket = parent_.output_bnode_.output_socket(start_bsocket_i + i);
set_default_value_for_output_socket(params, geometry_output_param + i, bsocket);
}
return false;
}
bool any_output_used = false;
for (const int i : IndexRange(1 + generation_items_range.size())) {
const lf::ValueUsage usage = params.get_output_usage(geometry_output_param + i);
if (usage == lf::ValueUsage::Used) {
any_output_used = true;
break;
}
}
if (!any_output_used) {
/* Only execute below if we are sure that the output is actually needed. */
return false;
}
const int bodies_num = eval_storage_.lf_body_nodes.size();
/* Check if all inputs are available, and request them if not. */
bool has_missing_input = false;
for (const int body_i : IndexRange(bodies_num)) {
const int offset = body_i * body_main_outputs_num +
parent_.indices_.generation.lf_inner[geometry_item_i];
for (const int i : IndexRange(1 + generation_items_range.size())) {
const bool is_available = params.try_get_input_data_ptr_or_request(offset + i) != nullptr;
if (!is_available) {
has_missing_input = true;
}
}
}
if (has_missing_input) {
/* Come back when all inputs are available. */
return false;
}
return true;
}
/**
* Logs intermediate values from the lazy-function graph evaluation into #GeoModifierLog based on
* the mapping between the lazy-function graph and the corresponding #bNodeTree.
@@ -2315,6 +3588,9 @@ struct GeometryNodesLazyFunctionBuilder {
this->build_repeat_zone_function(zone);
break;
}
case GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT:
this->build_foreach_geometry_element_zone_function(zone);
break;
default: {
BLI_assert_unreachable();
break;
@@ -2487,18 +3763,29 @@ struct GeometryNodesLazyFunctionBuilder {
{
ZoneBuildInfo &zone_info = zone_build_infos_[zone.index];
/* Build a function for the loop body. */
ZoneBodyFunction &body_fn = this->build_zone_body_function(zone);
ZoneBodyFunction &body_fn = this->build_zone_body_function(zone, "Repeat Body");
/* Wrap the loop body by another function that implements the repeat behavior. */
auto &zone_fn = scope_.construct<LazyFunctionForRepeatZone>(btree_, zone, zone_info, body_fn);
zone_info.lazy_function = &zone_fn;
}
void build_foreach_geometry_element_zone_function(const bNodeTreeZone &zone)
{
ZoneBuildInfo &zone_info = zone_build_infos_[zone.index];
/* Build a function for the loop body. */
ZoneBodyFunction &body_fn = this->build_zone_body_function(zone, "Foreach Body");
/* Wrap the loop body in another function that implements the foreach behavior. */
auto &zone_fn = scope_.construct<LazyFunctionForForeachGeometryElementZone>(
btree_, zone, zone_info, body_fn);
zone_info.lazy_function = &zone_fn;
}
/**
* Build a lazy-function for the "body" of a zone, i.e. for all the nodes within the zone.
*/
ZoneBodyFunction &build_zone_body_function(const bNodeTreeZone &zone)
ZoneBodyFunction &build_zone_body_function(const bNodeTreeZone &zone, const StringRef name)
{
lf::Graph &lf_body_graph = scope_.construct<lf::Graph>("Repeat Body");
lf::Graph &lf_body_graph = scope_.construct<lf::Graph>(name);
BuildGraphParams graph_params{lf_body_graph};
@@ -2506,7 +3793,10 @@ struct GeometryNodesLazyFunctionBuilder {
Vector<lf::GraphOutputSocket *> lf_body_outputs;
ZoneBodyFunction &body_fn = scope_.construct<ZoneBodyFunction>();
for (const bNodeSocket *bsocket : zone.input_node->output_sockets().drop_back(1)) {
for (const bNodeSocket *bsocket : zone.input_node->output_sockets()) {
if (ignore_zone_bsocket(*bsocket)) {
continue;
}
lf::GraphInputSocket &lf_input = lf_body_graph.add_input(
*bsocket->typeinfo->geometry_nodes_cpp_type, bsocket->name);
lf::GraphOutputSocket &lf_input_usage = lf_body_graph.add_output(
@@ -2522,7 +3812,10 @@ struct GeometryNodesLazyFunctionBuilder {
this->build_zone_border_link_input_usages(
zone, lf_body_graph, lf_body_outputs, body_fn.indices.outputs.border_link_usages);
for (const bNodeSocket *bsocket : zone.output_node->input_sockets().drop_back(1)) {
for (const bNodeSocket *bsocket : zone.output_node->input_sockets()) {
if (ignore_zone_bsocket(*bsocket)) {
continue;
}
lf::GraphOutputSocket &lf_output = lf_body_graph.add_output(
*bsocket->typeinfo->geometry_nodes_cpp_type, bsocket->name);
lf::GraphInputSocket &lf_output_usage = lf_body_graph.add_input(
@@ -2537,17 +3830,25 @@ struct GeometryNodesLazyFunctionBuilder {
this->insert_nodes_and_zones(zone.child_nodes, zone.child_zones, graph_params);
this->build_output_socket_usages(*zone.input_node, graph_params);
for (const int i : zone.input_node->output_sockets().drop_back(1).index_range()) {
const bNodeSocket &bsocket = zone.input_node->output_socket(i);
lf::OutputSocket *lf_usage = graph_params.usage_by_bsocket.lookup_default(&bsocket, nullptr);
lf::GraphOutputSocket &lf_usage_output =
*lf_body_outputs[body_fn.indices.outputs.input_usages[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);
{
int valid_socket_i = 0;
for (const bNodeSocket *bsocket : zone.input_node->output_sockets()) {
if (ignore_zone_bsocket(*bsocket)) {
continue;
}
lf::OutputSocket *lf_usage = graph_params.usage_by_bsocket.lookup_default(bsocket,
nullptr);
lf::GraphOutputSocket &lf_usage_output =
*lf_body_outputs[body_fn.indices.outputs.input_usages[valid_socket_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);
}
valid_socket_i++;
}
}
@@ -3132,33 +4433,45 @@ struct GeometryNodesLazyFunctionBuilder {
*child_zone_info.lazy_function);
mapping_->zone_node_map.add_new(&child_zone, &child_zone_node);
for (const int i : child_zone_info.indices.inputs.main.index_range()) {
const bNodeSocket &bsocket = child_zone.input_node->input_socket(i);
lf::InputSocket &lf_input_socket = child_zone_node.input(
child_zone_info.indices.inputs.main[i]);
lf::OutputSocket &lf_usage_socket = child_zone_node.output(
child_zone_info.indices.outputs.input_usages[i]);
mapping_->bsockets_by_lf_socket_map.add(&lf_input_socket, &bsocket);
graph_params.lf_inputs_by_bsocket.add(&bsocket, &lf_input_socket);
graph_params.usage_by_bsocket.add(&bsocket, &lf_usage_socket);
}
for (const int i : child_zone_info.indices.outputs.main.index_range()) {
const bNodeSocket &bsocket = child_zone.output_node->output_socket(i);
lf::OutputSocket &lf_output_socket = child_zone_node.output(
child_zone_info.indices.outputs.main[i]);
lf::InputSocket &lf_usage_input = child_zone_node.input(
child_zone_info.indices.inputs.output_usages[i]);
mapping_->bsockets_by_lf_socket_map.add(&lf_output_socket, &bsocket);
graph_params.lf_output_by_bsocket.add(&bsocket, &lf_output_socket);
graph_params.socket_usage_inputs.add(&lf_usage_input);
if (lf::OutputSocket *lf_usage = graph_params.usage_by_bsocket.lookup_default(&bsocket,
nullptr))
{
graph_params.lf_graph.add_link(*lf_usage, lf_usage_input);
{
int valid_socket_i = 0;
for (const bNodeSocket *bsocket : child_zone.input_node->input_sockets()) {
if (ignore_zone_bsocket(*bsocket)) {
continue;
}
lf::InputSocket &lf_input_socket = child_zone_node.input(
child_zone_info.indices.inputs.main[valid_socket_i]);
lf::OutputSocket &lf_usage_socket = child_zone_node.output(
child_zone_info.indices.outputs.input_usages[valid_socket_i]);
mapping_->bsockets_by_lf_socket_map.add(&lf_input_socket, bsocket);
graph_params.lf_inputs_by_bsocket.add(bsocket, &lf_input_socket);
graph_params.usage_by_bsocket.add(bsocket, &lf_usage_socket);
valid_socket_i++;
}
else {
static const bool static_false = false;
lf_usage_input.set_default_value(&static_false);
}
{
int valid_socket_i = 0;
for (const bNodeSocket *bsocket : child_zone.output_node->output_sockets()) {
if (ignore_zone_bsocket(*bsocket)) {
continue;
}
lf::OutputSocket &lf_output_socket = child_zone_node.output(
child_zone_info.indices.outputs.main[valid_socket_i]);
lf::InputSocket &lf_usage_input = child_zone_node.input(
child_zone_info.indices.inputs.output_usages[valid_socket_i]);
mapping_->bsockets_by_lf_socket_map.add(&lf_output_socket, bsocket);
graph_params.lf_output_by_bsocket.add(bsocket, &lf_output_socket);
graph_params.socket_usage_inputs.add(&lf_usage_input);
if (lf::OutputSocket *lf_usage = graph_params.usage_by_bsocket.lookup_default(bsocket,
nullptr))
{
graph_params.lf_graph.add_link(*lf_usage, lf_usage_input);
}
else {
static const bool static_false = false;
lf_usage_input.set_default_value(&static_false);
}
valid_socket_i++;
}
}
@@ -4597,6 +5910,11 @@ std::optional<FoundNestedNodeID> find_nested_node_id(const GeoNodesLFUserData &u
else if (dynamic_cast<const bke::SimulationZoneComputeContext *>(context) != nullptr) {
found.is_in_simulation = true;
}
else if (dynamic_cast<const bke::ForeachGeometryElementZoneComputeContext *>(context) !=
nullptr)
{
found.is_in_loop = true;
}
}
std::reverse(node_ids.begin(), node_ids.end());
node_ids.append(node_id);

View File

@@ -597,20 +597,26 @@ GeoTreeLogger &GeoModifierLog::get_local_tree_logger(const ComputeContext &compu
GeoTreeLogger &parent_logger = this->get_local_tree_logger(*parent_compute_context);
parent_logger.children_hashes.append(compute_context.hash());
}
if (const bke::GroupNodeComputeContext *node_group_compute_context =
if (const bke::GroupNodeComputeContext *typed_compute_context =
dynamic_cast<const bke::GroupNodeComputeContext *>(&compute_context))
{
tree_logger.parent_node_id.emplace(node_group_compute_context->node_id());
tree_logger.parent_node_id.emplace(typed_compute_context->node_id());
}
else if (const bke::RepeatZoneComputeContext *node_group_compute_context =
else if (const bke::RepeatZoneComputeContext *typed_compute_context =
dynamic_cast<const bke::RepeatZoneComputeContext *>(&compute_context))
{
tree_logger.parent_node_id.emplace(node_group_compute_context->output_node_id());
tree_logger.parent_node_id.emplace(typed_compute_context->output_node_id());
}
else if (const bke::SimulationZoneComputeContext *node_group_compute_context =
else if (const bke::ForeachGeometryElementZoneComputeContext *typed_compute_context =
dynamic_cast<const bke::ForeachGeometryElementZoneComputeContext *>(
&compute_context))
{
tree_logger.parent_node_id.emplace(typed_compute_context->output_node_id());
}
else if (const bke::SimulationZoneComputeContext *typed_compute_context =
dynamic_cast<const bke::SimulationZoneComputeContext *>(&compute_context))
{
tree_logger.parent_node_id.emplace(node_group_compute_context->output_node_id());
tree_logger.parent_node_id.emplace(typed_compute_context->output_node_id());
}
return tree_logger;
}
@@ -648,6 +654,13 @@ static void find_tree_zone_hash_recursive(
storage.inspection_index);
break;
}
case GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT: {
const auto &storage = *static_cast<const NodeGeometryForeachGeometryElementOutput *>(
zone.output_node->storage);
compute_context_builder.push<bke::ForeachGeometryElementZoneComputeContext>(
*zone.output_node, storage.inspection_index);
break;
}
}
r_hash_by_zone.add_new(&zone, compute_context_builder.hash());
for (const bNodeTreeZone *child_zone : zone.child_zones) {

View File

@@ -795,10 +795,10 @@ static bool group_input_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *li
{
BLI_assert(link->tonode != node);
BLI_assert(link->tosock->in_out == SOCK_IN);
if (link->fromsock->identifier != StringRef("__extend__")) {
if (!StringRef(link->fromsock->identifier).startswith("__extend__")) {
return true;
}
if (link->tosock->identifier == StringRef("__extend__")) {
if (StringRef(link->tosock->identifier).startswith("__extend__")) {
/* Don't connect to other "extend" sockets. */
return false;
}
@@ -816,10 +816,10 @@ static bool group_output_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *l
{
BLI_assert(link->fromnode != node);
BLI_assert(link->fromsock->in_out == SOCK_OUT);
if (link->tosock->identifier != StringRef("__extend__")) {
if (!StringRef(link->tosock->identifier).startswith("__extend__")) {
return true;
}
if (link->fromsock->identifier == StringRef("__extend__")) {
if (StringRef(link->fromsock->identifier).startswith("__extend__")) {
/* Don't connect to other "extend" sockets. */
return false;
}

View File

@@ -84,12 +84,33 @@ class RepeatZoneType : public blender::bke::bNodeZoneType {
}
};
class ForeachGeometryElementZoneType : public blender::bke::bNodeZoneType {
public:
ForeachGeometryElementZoneType()
{
this->input_idname = "GeometryNodeForeachGeometryElementInput";
this->output_idname = "GeometryNodeForeachGeometryElementOutput";
this->input_type = GEO_NODE_FOREACH_GEOMETRY_ELEMENT_INPUT;
this->output_type = GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT;
this->theme_id = TH_NODE_ZONE_FOREACH_GEOMETRY_ELEMENT;
}
const int &get_corresponding_output_id(const bNode &input_bnode) const override
{
BLI_assert(input_bnode.type == this->input_type);
return static_cast<NodeGeometryForeachGeometryElementInput *>(input_bnode.storage)
->output_node_id;
}
};
static void register_zone_types()
{
static SimulationZoneType simulation_zone_type;
static RepeatZoneType repeat_zone_type;
static ForeachGeometryElementZoneType foreach_geometry_element_zone_type;
blender::bke::register_node_zone_type(simulation_zone_type);
blender::bke::register_node_zone_type(repeat_zone_type);
blender::bke::register_node_zone_type(foreach_geometry_element_zone_type);
}
void register_nodes()

View File

@@ -927,6 +927,7 @@ set(geo_node_tests
curve_primitives
curves
curves/interpolate_curves
foreach_geometry_element_zone
geometry
grease_pencil
instance