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
@@ -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),
+20 -5
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,
+7
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"
@@ -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)
@@ -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
@@ -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";
+2
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
/** \} */
+2 -1
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,
@@ -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)
+12
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;
@@ -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;
}
}
}
};
@@ -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) {
@@ -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: {
@@ -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.
@@ -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,
@@ -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;
+18 -1
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);
@@ -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);
+33 -2
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;
}
+1 -1
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
+1 -1
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_;
+2
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
@@ -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
@@ -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
+73
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;
+1 -2
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];
@@ -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;
@@ -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);
@@ -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);
@@ -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)
+26 -3
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);
}
}
@@ -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;
};
/**
+11 -4
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);
}
+2
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")
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large Load Diff
@@ -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) {
+4 -4
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;
}
@@ -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()
+1
View File
@@ -927,6 +927,7 @@ set(geo_node_tests
curve_primitives
curves
curves/interpolate_curves
foreach_geometry_element_zone
geometry
grease_pencil
instance