Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,407 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
..
include
../intern
../../editors/include
../../compositor
../../makesrna
../../compositor/algorithms
../../compositor/cached_resources
../../compositor/derived_resources
../../compositor/utilities
../../modifiers
../../io/common
../../io/csv
../../io/ply
../../io/stl
../../io/wavefront_obj
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
nodes/node_geo_accumulate_field.cc
nodes/node_geo_attribute_capture.cc
nodes/node_geo_attribute_domain_size.cc
nodes/node_geo_attribute_statistic.cc
nodes/node_geo_bake.cc
nodes/node_geo_blur_attribute.cc
nodes/node_geo_bone_info.cc
nodes/node_geo_boolean.cc
nodes/node_geo_bounding_box.cc
nodes/node_geo_camera_info.cc
nodes/node_geo_closure_to_list.cc
nodes/node_geo_closure.cc
nodes/node_geo_cluster_by_connected.cc
nodes/node_geo_cluster_by_distance.cc
nodes/node_geo_collection_children.cc
nodes/node_geo_collection_info.cc
nodes/node_geo_combine_bundle.cc
nodes/node_geo_common.cc
nodes/node_geo_convex_hull.cc
nodes/node_geo_cube_grid_topology.cc
nodes/node_geo_curve_endpoint_selection.cc
nodes/node_geo_curve_fill.cc
nodes/node_geo_curve_fillet.cc
nodes/node_geo_curve_handle_type_selection.cc
nodes/node_geo_curve_length.cc
nodes/node_geo_curve_primitive_arc.cc
nodes/node_geo_curve_primitive_bezier_segment.cc
nodes/node_geo_curve_primitive_circle.cc
nodes/node_geo_curve_primitive_line.cc
nodes/node_geo_curve_primitive_quadratic_bezier.cc
nodes/node_geo_curve_primitive_quadrilateral.cc
nodes/node_geo_curve_primitive_spiral.cc
nodes/node_geo_curve_primitive_star.cc
nodes/node_geo_curve_resample.cc
nodes/node_geo_curve_reverse.cc
nodes/node_geo_curve_sample.cc
nodes/node_geo_curve_set_handle_type.cc
nodes/node_geo_curve_spline_parameter.cc
nodes/node_geo_curve_spline_type.cc
nodes/node_geo_curve_subdivide.cc
nodes/node_geo_curve_to_mesh.cc
nodes/node_geo_curve_to_points.cc
nodes/node_geo_curve_topology_curve_of_point.cc
nodes/node_geo_curve_topology_points_of_curve.cc
nodes/node_geo_curve_trim.cc
nodes/node_geo_curves_to_grease_pencil.cc
nodes/node_geo_deform_curves_on_surface.cc
nodes/node_geo_delete_geometry.cc
nodes/node_geo_distribute_points_in_grid.cc
nodes/node_geo_distribute_points_in_volume.cc
nodes/node_geo_distribute_points_on_faces.cc
nodes/node_geo_dual_mesh.cc
nodes/node_geo_duplicate_elements.cc
nodes/node_geo_edge_paths_to_curves.cc
nodes/node_geo_edge_paths_to_selection.cc
nodes/node_geo_edge_split.cc
nodes/node_geo_edges_to_face_groups.cc
nodes/node_geo_enable_output.cc
nodes/node_geo_evaluate_at_index.cc
nodes/node_geo_evaluate_closure.cc
nodes/node_geo_evaluate_on_domain.cc
nodes/node_geo_extrude_mesh.cc
nodes/node_geo_field_average.cc
nodes/node_geo_field_min_and_max.cc
nodes/node_geo_field_to_grid.cc
nodes/node_geo_field_to_list.cc
nodes/node_geo_filter_list.cc
nodes/node_geo_field_variance.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_attribute_names.cc
nodes/node_geo_get_bundle_item.cc
nodes/node_geo_get_geometry_bundle.cc
nodes/node_geo_get_geometry_component.cc
nodes/node_geo_get_named_grid.cc
nodes/node_geo_get_nested_bundle_paths.cc
nodes/node_geo_gizmo_dial.cc
nodes/node_geo_gizmo_linear.cc
nodes/node_geo_gizmo_transform.cc
nodes/node_geo_grease_pencil_to_curves.cc
nodes/node_geo_grid_advect.cc
nodes/node_geo_grid_clip.cc
nodes/node_geo_grid_curl.cc
nodes/node_geo_grid_dilate_erode.cc
nodes/node_geo_grid_divergence.cc
nodes/node_geo_grid_gradient.cc
nodes/node_geo_grid_info.cc
nodes/node_geo_grid_laplacian.cc
nodes/node_geo_grid_mean.cc
nodes/node_geo_grid_median.cc
nodes/node_geo_grid_prune.cc
nodes/node_geo_grid_to_mesh.cc
nodes/node_geo_grid_to_points.cc
nodes/node_geo_grid_voxelize.cc
nodes/node_geo_image.cc
nodes/node_geo_image_info.cc
nodes/node_geo_image_texture.cc
nodes/node_geo_import_csv.cc
nodes/node_geo_import_obj.cc
nodes/node_geo_import_ply.cc
nodes/node_geo_import_stl.cc
nodes/node_geo_import_text.cc
nodes/node_geo_import_vdb.cc
nodes/node_geo_index_of_nearest.cc
nodes/node_geo_index_switch.cc
nodes/node_geo_input_active_camera.cc
nodes/node_geo_input_collection.cc
nodes/node_geo_input_curve_handles.cc
nodes/node_geo_input_curve_tilt.cc
nodes/node_geo_input_edge_smooth.cc
nodes/node_geo_input_face_smooth.cc
nodes/node_geo_input_font.cc
nodes/node_geo_input_id.cc
nodes/node_geo_input_index.cc
nodes/node_geo_input_instance_bounds.cc
nodes/node_geo_input_instance_reference.cc
nodes/node_geo_input_instance_rotation.cc
nodes/node_geo_input_instance_scale.cc
nodes/node_geo_input_instance_transform.cc
nodes/node_geo_input_material.cc
nodes/node_geo_input_material_index.cc
nodes/node_geo_input_mesh_edge_angle.cc
nodes/node_geo_input_mesh_edge_neighbors.cc
nodes/node_geo_input_mesh_edge_vertices.cc
nodes/node_geo_input_mesh_face_area.cc
nodes/node_geo_input_mesh_face_is_planar.cc
nodes/node_geo_input_mesh_face_neighbors.cc
nodes/node_geo_input_mesh_island.cc
nodes/node_geo_input_mesh_vertex_neighbors.cc
nodes/node_geo_input_named_attribute.cc
nodes/node_geo_input_named_layer_selection.cc
nodes/node_geo_input_normal.cc
nodes/node_geo_input_object.cc
nodes/node_geo_input_position.cc
nodes/node_geo_input_radius.cc
nodes/node_geo_input_scene_time.cc
nodes/node_geo_input_shortest_edge_paths.cc
nodes/node_geo_input_spline_cyclic.cc
nodes/node_geo_input_spline_length.cc
nodes/node_geo_input_spline_resolution.cc
nodes/node_geo_input_tangent.cc
nodes/node_geo_input_voxel_index.cc
nodes/node_geo_instance_on_points.cc
nodes/node_geo_instances_to_points.cc
nodes/node_geo_interpolate_curves.cc
nodes/node_geo_is_viewport.cc
nodes/node_geo_join_bundle.cc
nodes/node_geo_join_geometry.cc
nodes/node_geo_list_get_item.cc
nodes/node_geo_list_length.cc
nodes/node_geo_material_replace.cc
nodes/node_geo_material_selection.cc
nodes/node_geo_menu_switch.cc
nodes/node_geo_merge_by_distance.cc
nodes/node_geo_merge_layers.cc
nodes/node_geo_merge_points.cc
nodes/node_geo_mesh_bevel.cc
nodes/node_geo_mesh_face_group_boundaries.cc
nodes/node_geo_mesh_primitive_circle.cc
nodes/node_geo_mesh_primitive_cone.cc
nodes/node_geo_mesh_primitive_cube.cc
nodes/node_geo_mesh_primitive_cylinder.cc
nodes/node_geo_mesh_primitive_grid.cc
nodes/node_geo_mesh_primitive_ico_sphere.cc
nodes/node_geo_mesh_primitive_line.cc
nodes/node_geo_mesh_primitive_uv_sphere.cc
nodes/node_geo_mesh_subdivide.cc
nodes/node_geo_mesh_to_curve.cc
nodes/node_geo_mesh_to_density_grid.cc
nodes/node_geo_mesh_to_points.cc
nodes/node_geo_mesh_to_sdf_grid.cc
nodes/node_geo_mesh_to_volume.cc
nodes/node_geo_mesh_topology_corners_of_edge.cc
nodes/node_geo_mesh_topology_corners_of_face.cc
nodes/node_geo_mesh_topology_corners_of_vertex.cc
nodes/node_geo_mesh_topology_edges_of_corner.cc
nodes/node_geo_mesh_topology_edges_of_vertex.cc
nodes/node_geo_mesh_topology_face_of_corner.cc
nodes/node_geo_mesh_topology_offset_corner_in_face.cc
nodes/node_geo_mesh_topology_vertex_of_corner.cc
nodes/node_geo_mouse_position.cc
nodes/node_geo_object_info.cc
nodes/node_geo_offset_point_in_curve.cc
nodes/node_geo_points.cc
nodes/node_geo_points_to_curves.cc
nodes/node_geo_points_to_sdf_grid.cc
nodes/node_geo_points_to_vertices.cc
nodes/node_geo_points_to_volume.cc
nodes/node_geo_proximity.cc
nodes/node_geo_raycast.cc
nodes/node_geo_realize_instances.cc
nodes/node_geo_remove_attribute.cc
nodes/node_geo_rename_attribute.cc
nodes/node_geo_repeat.cc
nodes/node_geo_rotate_instances.cc
nodes/node_geo_sample_grid.cc
nodes/node_geo_sample_grid_index.cc
nodes/node_geo_sample_index.cc
nodes/node_geo_sample_nearest.cc
nodes/node_geo_sample_nearest_surface.cc
nodes/node_geo_sample_sound_frequencies.cc
nodes/node_geo_sample_uv_surface.cc
nodes/node_geo_scale_elements.cc
nodes/node_geo_scale_instances.cc
nodes/node_geo_sdf_grid_boolean.cc
nodes/node_geo_sdf_grid_fillet.cc
nodes/node_geo_sdf_grid_laplacian.cc
nodes/node_geo_sdf_grid_mean.cc
nodes/node_geo_sdf_grid_mean_curvature.cc
nodes/node_geo_sdf_grid_median.cc
nodes/node_geo_sdf_grid_offset.cc
nodes/node_geo_self_object.cc
nodes/node_geo_separate_bundle.cc
nodes/node_geo_separate_components.cc
nodes/node_geo_separate_geometry.cc
nodes/node_geo_set_curve_handles.cc
nodes/node_geo_set_curve_normal.cc
nodes/node_geo_set_curve_radius.cc
nodes/node_geo_set_curve_tilt.cc
nodes/node_geo_set_geometry_bundle.cc
nodes/node_geo_set_geometry_name.cc
nodes/node_geo_set_grease_pencil_color.cc
nodes/node_geo_set_grease_pencil_depth_mode.cc
nodes/node_geo_set_grease_pencil_softness.cc
nodes/node_geo_set_grid_background.cc
nodes/node_geo_set_grid_transform.cc
nodes/node_geo_set_id.cc
nodes/node_geo_set_instance_transform.cc
nodes/node_geo_set_material.cc
nodes/node_geo_set_material_index.cc
nodes/node_geo_set_mesh_normal.cc
nodes/node_geo_set_nurbs_order.cc
nodes/node_geo_set_nurbs_weight.cc
nodes/node_geo_set_point_radius.cc
nodes/node_geo_set_position.cc
nodes/node_geo_set_shade_smooth.cc
nodes/node_geo_set_spline_cyclic.cc
nodes/node_geo_set_spline_resolution.cc
nodes/node_geo_simulation.cc
nodes/node_geo_sort_elements.cc
nodes/node_geo_sort_list.cc
nodes/node_geo_split_to_instances.cc
nodes/node_geo_store_bundle_item.cc
nodes/node_geo_store_named_attribute.cc
nodes/node_geo_store_named_grid.cc
nodes/node_geo_string_join.cc
nodes/node_geo_string_to_curves.cc
nodes/node_geo_subdivision_surface.cc
nodes/node_geo_switch.cc
nodes/node_geo_tag_filter.cc
nodes/node_geo_tool_3d_cursor.cc
nodes/node_geo_tool_active_element.cc
nodes/node_geo_tool_face_set.cc
nodes/node_geo_tool_selection.cc
nodes/node_geo_tool_set_face_set.cc
nodes/node_geo_tool_set_selection.cc
nodes/node_geo_transform_geometry.cc
nodes/node_geo_translate_instances.cc
nodes/node_geo_transfer_attributes.cc
nodes/node_geo_triangulate.cc
nodes/node_geo_uv_pack_islands.cc
nodes/node_geo_uv_tangent.cc
nodes/node_geo_uv_unwrap.cc
nodes/node_geo_viewer.cc
nodes/node_geo_viewport_transform.cc
nodes/node_geo_volume_cube.cc
nodes/node_geo_volume_to_mesh.cc
nodes/node_geo_warning.cc
nodes/node_geo_xpbd_solver.cc
include/NOD_geo_bake.hh
include/NOD_geo_bundle.hh
include/NOD_geo_capture_attribute.hh
include/NOD_geo_closure.hh
include/NOD_geo_closure_to_list.hh
include/NOD_geo_field_to_grid.hh
include/NOD_geo_field_to_list.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
include/NOD_geo_simulation.hh
include/NOD_geo_tag_filter.hh
include/NOD_geo_viewer.hh
node_geometry_tree.cc
node_geometry_util.cc
node_geometry_util.hh
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blenloader
PRIVATE bf::blentranslation
PRIVATE bf::bmesh
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::functions
PRIVATE bf::geometry
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::imbuf::movie
PRIVATE bf::intern::guardedalloc
bf_nodes
bf_compositor
bf_io_common
bf_io_csv
PRIVATE bf::intern::atomic
PRIVATE bf::extern::xxhash
PRIVATE bf::render
PRIVATE bf::windowmanager
PRIVATE bf::dependencies::optional::gmp
PRIVATE bf::dependencies::optional::bullet
)
if(WITH_IO_STL)
list(APPEND LIB
PRIVATE bf_io_common
PRIVATE bf_io_stl
)
add_definitions(-DWITH_IO_STL)
endif()
if(WITH_IO_PLY)
list(APPEND LIB
PRIVATE bf_io_common
PRIVATE bf_io_ply
)
add_definitions(-DWITH_IO_PLY)
endif()
if(WITH_IO_WAVEFRONT_OBJ)
list(APPEND LIB
PRIVATE bf_io_common
PRIVATE bf_io_wavefront_obj
)
add_definitions(-DWITH_IO_WAVEFRONT_OBJ)
endif()
if(WITH_BULLET)
list(APPEND INC
../../../../intern/rigidbody
)
endif()
if(WITH_OPENSUBDIV)
add_definitions(-DWITH_OPENSUBDIV)
endif()
add_node_discovery(
bf_nodes_geometry_generated
"${SRC}"
${CMAKE_CURRENT_BINARY_DIR}/register_geometry_nodes.cc
register_geometry_nodes
)
list(APPEND LIB
bf_nodes_geometry_generated
)
# If compiling with MSVC clang we need to add the `D_LIBCPP_VERSION` define
# so we don't run into TBB errors when compiling with lib.
if(WITH_TBB AND MSVC_CLANG)
string(APPEND CMAKE_CXX_FLAGS " /D_LIBCPP_VERSION")
endif()
blender_add_lib(bf_nodes_geometry "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
target_link_libraries(bf_nodes_geometry_generated bf_nodes_geometry)
blender_set_target_unity_build(bf_nodes_geometry 10)
# RNA_prototypes.hh
add_dependencies(bf_nodes_geometry bf_rna)

View File

@@ -0,0 +1,132 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <optional>
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "NOD_geo_simulation.hh"
#include "NOD_socket_items.hh"
#include "RNA_access.hh"
#include "BLI_index_range.hh"
namespace blender {
struct NodesModifierData;
struct NodesModifierBake;
struct SpaceNode;
struct Object;
namespace nodes {
/**
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) for index
* bake node items.
*/
struct BakeItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeGeometryBakeItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeBake";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_bake_node_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_bake_node_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_bake_node_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_bake_node_items";
};
struct rna_names {
static constexpr StringRefNull items = "bake_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<NodeGeometryBakeItem> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryBake *>(node.storage);
return {&storage->items, &storage->items_num, &storage->active_index};
}
static void copy_item(const NodeGeometryBakeItem &src, NodeGeometryBakeItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(NodeGeometryBakeItem *item)
{
MEM_SAFE_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const NodeGeometryBakeItem &item)
{
return item.socket_type;
}
static char **get_name(NodeGeometryBakeItem &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type)
{
return SimulationItemsAccessor::supports_socket_type(socket_type, ntree_type);
}
static void init_with_socket_type_and_name(bNode &node,
NodeGeometryBakeItem &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryBake *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<BakeItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const NodeGeometryBakeItem &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
struct BakeDrawContext {
const bNode *node;
SpaceNode *snode;
const Object *object;
const NodesModifierData *nmd;
const NodesModifierBake *bake;
PointerRNA bake_rna;
std::optional<IndexRange> baked_range;
std::optional<IndexRange> frame_range;
bool bake_still;
bool is_baked;
std::optional<NodesModifierBakeTarget> bake_target;
bool is_bakeable_in_current_context;
};
[[nodiscard]] bool get_bake_draw_context(const bContext *C,
const bNode &node,
BakeDrawContext &r_ctx);
std::string get_baked_string(const BakeDrawContext &ctx);
std::optional<std::string> get_bake_state_string(const BakeDrawContext &ctx);
void draw_common_bake_settings(bContext *C, BakeDrawContext &ctx, ui::Layout &layout);
void draw_bake_button_row(const BakeDrawContext &ctx,
ui::Layout &layout,
bool is_in_sidebar = false);
} // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,176 @@
/* SPDX-FileCopyrightText: 2025 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 {
inline bool socket_type_supported_in_bundle(const eNodeSocketDatatype socket_type,
const int ntree_type)
{
return bke::node_tree_type_supports_socket_type_static(ntree_type, socket_type);
}
struct CombineBundleItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeCombineBundleItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "NodeCombineBundle";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_name_validation = true;
static constexpr char unique_name_separator = '_';
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_combine_bundle_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_combine_bundle_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_combine_bundle_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_combine_bundle_items";
};
struct rna_names {
static constexpr StringRefNull items = "bundle_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeCombineBundle *>(node.storage);
return {&storage->items, &storage->items_num, &storage->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_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return item.socket_type;
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type)
{
return socket_type_supported_in_bundle(socket_type, ntree_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeCombineBundle *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<CombineBundleItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
static std::string validate_name(const StringRef name);
};
struct SeparateBundleItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeSeparateBundleItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "NodeSeparateBundle";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_name_validation = true;
static constexpr char unique_name_separator = '_';
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_separate_bundle_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_separate_bundle_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_separate_bundle_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_separate_bundle_items";
};
struct rna_names {
static constexpr StringRefNull items = "bundle_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeSeparateBundle *>(node.storage);
return {&storage->items, &storage->items_num, &storage->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_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return item.socket_type;
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type)
{
return socket_type_supported_in_bundle(socket_type, ntree_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeSeparateBundle *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<SeparateBundleItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
static std::string validate_name(const StringRef name)
{
return CombineBundleItemsAccessor::validate_name(name);
}
};
std::optional<StringRefNull> combine_bundle_node_type(const bNodeTree &tree, const bNode &node);
} // namespace blender::nodes

View File

@@ -0,0 +1,105 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DNA_node_types.h"
#include "NOD_socket_items.hh"
#include "BKE_customdata.hh"
namespace blender::nodes {
struct CaptureAttributeItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeGeometryAttributeCaptureItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeCaptureAttribute";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = false;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_capture_attribute_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_capture_attribute_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_capture_attribute_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "NODE_UL_capture_items_list";
};
struct rna_names {
static constexpr StringRefNull items = "capture_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<NodeGeometryAttributeCaptureItem> get_items_from_node(
bNode &node)
{
auto *storage = static_cast<NodeGeometryAttributeCapture *>(node.storage);
return {&storage->capture_items, &storage->capture_items_num, &storage->active_index};
}
static void copy_item(const NodeGeometryAttributeCaptureItem &src,
NodeGeometryAttributeCaptureItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(NodeGeometryAttributeCaptureItem *item)
{
MEM_SAFE_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const NodeGeometryAttributeCaptureItem &item)
{
return *bke::custom_data_type_to_socket_type(eCustomDataType(item.data_type));
}
static char **get_name(NodeGeometryAttributeCaptureItem &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/)
{
return bke::socket_type_to_custom_data_type(socket_type).has_value() &&
socket_type != SOCK_STRING;
}
static void init_with_socket_type_and_name(bNode &node,
NodeGeometryAttributeCaptureItem &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryAttributeCapture *>(node.storage);
item.data_type = *bke::socket_type_to_custom_data_type(socket_type);
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<CaptureAttributeItemsAccessor>(node, item, name);
}
static std::string input_socket_identifier_for_item(const NodeGeometryAttributeCaptureItem &item)
{
if (item.identifier == 0) {
/* This special case exists for compatibility. */
return "Value";
}
return "Value_" + std::to_string(item.identifier);
}
static std::string output_socket_identifier_for_item(
const NodeGeometryAttributeCaptureItem &item)
{
if (item.identifier == 0) {
/* This special case exists for compatibility. */
return "Attribute";
}
return "Attribute_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@@ -0,0 +1,325 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DNA_node_types.h"
#include "NOD_socket_items.hh"
#include "BKE_node.hh"
namespace blender::nodes {
inline bool socket_type_supported_in_closure(const eNodeSocketDatatype socket_type,
const int ntree_type)
{
return bke::node_tree_type_supports_socket_type_static(ntree_type, socket_type);
}
struct ClosureInputItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeClosureInputItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "NodeClosureOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_closure_input_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_closure_input_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_closure_input_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_closure_input_items";
};
struct rna_names {
static constexpr StringRefNull items = "input_items";
static constexpr StringRefNull active_index = "active_input_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeClosureOutput *>(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_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return item.socket_type;
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type)
{
return socket_type_supported_in_closure(socket_type, ntree_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeClosureOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->input_items.next_identifier++;
socket_items::set_item_name_and_make_unique<ClosureInputItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
struct ClosureOutputItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeClosureOutputItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "NodeClosureOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_closure_output_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_closure_output_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_closure_output_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_closure_output_items";
};
struct rna_names {
static constexpr StringRefNull items = "output_items";
static constexpr StringRefNull active_index = "active_output_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeClosureOutput *>(node.storage);
return {&storage->output_items.items,
&storage->output_items.items_num,
&storage->output_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_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return item.socket_type;
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type)
{
return socket_type_supported_in_closure(socket_type, ntree_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeClosureOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->output_items.next_identifier++;
socket_items::set_item_name_and_make_unique<ClosureOutputItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
struct EvaluateClosureInputItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeEvaluateClosureInputItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "NodeEvaluateClosure";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_evaluate_closure_input_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_evaluate_closure_input_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_evaluate_closure_input_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_evaluate_closure_input_items";
};
struct rna_names {
static constexpr StringRefNull items = "input_items";
static constexpr StringRefNull active_index = "active_input_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeEvaluateClosure *>(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_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return item.socket_type;
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type)
{
return socket_type_supported_in_closure(socket_type, ntree_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeEvaluateClosure *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->input_items.next_identifier++;
socket_items::set_item_name_and_make_unique<EvaluateClosureInputItemsAccessor>(
node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
struct EvaluateClosureOutputItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeEvaluateClosureOutputItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "NodeEvaluateClosure";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_evaluate_closure_output_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_evaluate_closure_output_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_evaluate_closure_output_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_evaluate_closure_output_items";
};
struct rna_names {
static constexpr StringRefNull items = "output_items";
static constexpr StringRefNull active_index = "active_output_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeEvaluateClosure *>(node.storage);
return {&storage->output_items.items,
&storage->output_items.items_num,
&storage->output_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_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return item.socket_type;
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type)
{
return socket_type_supported_in_closure(socket_type, ntree_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeEvaluateClosure *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->output_items.next_identifier++;
socket_items::set_item_name_and_make_unique<EvaluateClosureOutputItemsAccessor>(
node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
/**
* Gets an input socket that can be considered to be internally linked to the given output, or
* null if there is none.
*/
const bNodeSocket *evaluate_closure_node_internally_linked_input(const bNodeSocket &output_socket);
} // namespace blender::nodes

View File

@@ -0,0 +1,98 @@
/* SPDX-FileCopyrightText: 2026 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 {
/**
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) for field
* to grid items.
*/
struct ClosureToListItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = GeometryNodeClosureToListItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeClosureToList";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = false;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_closure_to_list_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_closure_to_list_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_closure_to_list_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "NODE_UL_closure_to_list_items";
};
struct rna_names {
static constexpr StringRefNull items = "list_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<GeometryNodeClosureToListItem> get_items_from_node(
bNode &node)
{
auto &storage = *static_cast<GeometryNodeClosureToList *>(node.storage);
return {&storage.items, &storage.items_num, &storage.active_index};
}
static void copy_item(const GeometryNodeClosureToListItem &src,
GeometryNodeClosureToListItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(GeometryNodeClosureToListItem *item)
{
MEM_SAFE_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return eNodeSocketDatatype(item.socket_type);
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type)
{
return bke::node_tree_type_supports_socket_type_static(ntree_type, socket_type);
}
static char **get_name(GeometryNodeClosureToListItem &item)
{
return &item.name;
}
static void init_with_socket_type_and_name(bNode &node,
GeometryNodeClosureToListItem &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<GeometryNodeClosureToList *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->next_identifier++;
item.structure_type = NodeSocketInterfaceStructureType::Single;
socket_items::set_item_name_and_make_unique<ClosureToListItemsAccessor>(node, item, name);
}
static std::string input_socket_identifier_for_item(
const GeometryNodeClosureToListItem & /*item*/)
{
return {};
}
static std::string output_socket_identifier_for_item(const GeometryNodeClosureToListItem &item)
{
return "List_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@@ -0,0 +1,95 @@
/* SPDX-FileCopyrightText: 2025 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 {
/**
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) for field
* to grid items.
*/
struct FieldToGridItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = GeometryNodeFieldToGridItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeFieldToGrid";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = false;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_field_to_grid_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_field_to_grid_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_field_to_grid_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "NODE_UL_field_to_grid_items";
};
struct rna_names {
static constexpr StringRefNull items = "grid_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<GeometryNodeFieldToGridItem> get_items_from_node(bNode &node)
{
auto &storage = *static_cast<GeometryNodeFieldToGrid *>(node.storage);
return {&storage.items, &storage.items_num, &storage.active_index};
}
static void copy_item(const GeometryNodeFieldToGridItem &src, GeometryNodeFieldToGridItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(GeometryNodeFieldToGridItem *item)
{
MEM_SAFE_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return item.data_type;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/)
{
return socket_type_supports_grids(socket_type);
}
static char **get_name(GeometryNodeFieldToGridItem &item)
{
return &item.name;
}
static void init_with_socket_type_and_name(bNode &node,
GeometryNodeFieldToGridItem &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<GeometryNodeFieldToGrid *>(node.storage);
item.data_type = socket_type;
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<FieldToGridItemsAccessor>(node, item, name);
}
static std::string input_socket_identifier_for_item(const GeometryNodeFieldToGridItem &item)
{
return "Field_" + std::to_string(item.identifier);
}
static std::string output_socket_identifier_for_item(const GeometryNodeFieldToGridItem &item)
{
return "Grid_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@@ -0,0 +1,95 @@
/* SPDX-FileCopyrightText: 2025 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 {
/**
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) for field
* to grid items.
*/
struct FieldToListItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = GeometryNodeFieldToListItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeFieldToList";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = false;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_field_to_list_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_field_to_list_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_field_to_list_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "NODE_UL_field_to_list_items";
};
struct rna_names {
static constexpr StringRefNull items = "list_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<GeometryNodeFieldToListItem> get_items_from_node(bNode &node)
{
auto &storage = *static_cast<GeometryNodeFieldToList *>(node.storage);
return {&storage.items, &storage.items_num, &storage.active_index};
}
static void copy_item(const GeometryNodeFieldToListItem &src, GeometryNodeFieldToListItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(GeometryNodeFieldToListItem *item)
{
MEM_SAFE_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return item.socket_type;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/)
{
return socket_type_supports_fields(socket_type);
}
static char **get_name(GeometryNodeFieldToListItem &item)
{
return &item.name;
}
static void init_with_socket_type_and_name(bNode &node,
GeometryNodeFieldToListItem &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<GeometryNodeFieldToList *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<FieldToListItemsAccessor>(node, item, name);
}
static std::string input_socket_identifier_for_item(const GeometryNodeFieldToListItem &item)
{
return "Field_" + std::to_string(item.identifier);
}
static std::string output_socket_identifier_for_item(const GeometryNodeFieldToListItem &item)
{
return "List_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@@ -0,0 +1,272 @@
/* 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
: public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeForeachGeometryElementInputItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeForeachGeometryElementOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item =
"NODE_OT_foreach_geometry_element_zone_input_item_add";
static constexpr StringRefNull remove_item =
"NODE_OT_foreach_geometry_element_zone_input_item_remove";
static constexpr StringRefNull move_item =
"NODE_OT_foreach_geometry_element_zone_input_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_foreach_geometry_element_input_items";
};
struct rna_names {
static constexpr StringRefNull items = "input_items";
static constexpr StringRefNull active_index = "active_input_index";
};
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_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return item.socket_type;
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/)
{
return ELEM(socket_type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_INT,
SOCK_MENU);
}
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 : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeForeachGeometryElementMainItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeForeachGeometryElementOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item =
"NODE_OT_foreach_geometry_element_zone_main_item_add";
static constexpr StringRefNull remove_item =
"NODE_OT_foreach_geometry_element_zone_main_item_remove";
static constexpr StringRefNull move_item =
"NODE_OT_foreach_geometry_element_zone_main_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_foreach_geometry_element_main_items";
};
struct rna_names {
static constexpr StringRefNull items = "main_items";
static constexpr StringRefNull active_index = "active_main_index";
};
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_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return item.socket_type;
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_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
: public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeForeachGeometryElementGenerationItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeForeachGeometryElementOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item =
"NODE_OT_foreach_geometry_element_zone_generation_item_add";
static constexpr StringRefNull remove_item =
"NODE_OT_foreach_geometry_element_zone_generation_item_remove";
static constexpr StringRefNull move_item =
"NODE_OT_foreach_geometry_element_zone_generation_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_foreach_geometry_element_generation_items";
};
struct rna_names {
static constexpr StringRefNull items = "generation_items";
static constexpr StringRefNull active_index = "active_generation_index";
};
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_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return item.socket_type;
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/)
{
return ELEM(socket_type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_INT,
SOCK_GEOMETRY);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryForeachGeometryElementOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->generation_items.next_identifier++;
socket_items::set_item_name_and_make_unique<ForeachGeometryElementGenerationItemsAccessor>(
node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Generation_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@@ -0,0 +1,58 @@
/* 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 {
/**
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) for index
* switch items.
*/
struct IndexSwitchItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = IndexSwitchItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeIndexSwitch";
static constexpr bool has_type = false;
static constexpr bool has_name = false;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_index_switch_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_index_switch_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_index_switch_item_move";
};
static socket_items::SocketItemsRef<IndexSwitchItem> get_items_from_node(bNode &node)
{
auto &storage = *static_cast<NodeIndexSwitch *>(node.storage);
return {&storage.items, &storage.items_num, nullptr};
}
static void copy_item(const IndexSwitchItem &src, IndexSwitchItem &dst)
{
dst = src;
}
static void destruct_item(IndexSwitchItem * /*item*/) {}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static void init(bNode &node, IndexSwitchItem &item)
{
auto &storage = *static_cast<NodeIndexSwitch *>(node.storage);
item.identifier = storage.next_identifier++;
}
static std::string socket_identifier_for_item(const IndexSwitchItem &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@@ -0,0 +1,80 @@
/* 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 {
/**
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) for menu
* switch node items.
*/
struct MenuSwitchItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeEnumItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeMenuSwitch";
static constexpr bool has_type = false;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_enum_definition_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_enum_definition_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_enum_definition_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "NODE_UL_enum_definition_items";
};
struct rna_names {
static constexpr StringRefNull items = "enum_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<NodeEnumItem> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeMenuSwitch *>(node.storage);
return {&storage->enum_definition.items_array,
&storage->enum_definition.items_num,
&storage->enum_definition.active_index};
}
static void copy_item(const NodeEnumItem &src, NodeEnumItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
dst.description = BLI_strdup_null(dst.description);
}
static void destruct_item(NodeEnumItem *item)
{
MEM_SAFE_DELETE(item->name);
MEM_SAFE_DELETE(item->description);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static char **get_name(NodeEnumItem &item)
{
return &item.name;
}
static void init_with_name(bNode &node, NodeEnumItem &item, const char *name)
{
auto *storage = static_cast<NodeMenuSwitch *>(node.storage);
item.identifier = storage->enum_definition.next_identifier++;
socket_items::set_item_name_and_make_unique<MenuSwitchItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const NodeEnumItem &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@@ -0,0 +1,89 @@
/* 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 {
/**
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) with
* repeat items.
*/
struct RepeatItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeRepeatItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeRepeatOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_repeat_zone_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_repeat_zone_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_repeat_zone_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_repeat_zone_state";
};
struct rna_names {
static constexpr StringRefNull items = "repeat_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<NodeRepeatItem> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryRepeatOutput *>(node.storage);
return {&storage->items, &storage->items_num, &storage->active_index};
}
static void copy_item(const NodeRepeatItem &src, NodeRepeatItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(NodeRepeatItem *item)
{
MEM_SAFE_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const NodeRepeatItem &item)
{
return item.socket_type;
}
static char **get_name(NodeRepeatItem &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type)
{
return bke::node_tree_type_supports_socket_type_static(ntree_type, socket_type);
}
static void init_with_socket_type_and_name(bNode &node,
NodeRepeatItem &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryRepeatOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<RepeatItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const NodeRepeatItem &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@@ -0,0 +1,100 @@
/* 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 {
/**
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) with
* simulation items.
*/
struct SimulationItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeSimulationItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeSimulationOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_simulation_zone_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_simulation_zone_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_simulation_zone_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "DATA_UL_simulation_zone_state";
};
struct rna_names {
static constexpr StringRefNull items = "state_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<NodeSimulationItem> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometrySimulationOutput *>(node.storage);
return {&storage->items, &storage->items_num, &storage->active_index};
}
static void copy_item(const NodeSimulationItem &src, NodeSimulationItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(NodeSimulationItem *item)
{
MEM_SAFE_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const NodeSimulationItem &item)
{
return item.socket_type;
}
static char **get_name(NodeSimulationItem &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/)
{
/* Data-block types and closures are not supported. */
return ELEM(socket_type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_INT,
SOCK_STRING,
SOCK_GEOMETRY,
SOCK_BUNDLE);
}
static void init_with_socket_type_and_name(bNode &node,
NodeSimulationItem &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometrySimulationOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<SimulationItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const NodeSimulationItem &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View File

@@ -0,0 +1,14 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
namespace blender::nodes {
bool tag_filter_matches(const StringRef tag_filter, const Set<std::string> &tags);
} // namespace blender::nodes

View File

@@ -0,0 +1,102 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DNA_node_types.h"
#include "NOD_eval_log.hh"
#include "NOD_socket_items.hh"
namespace blender::nodes {
/**
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) for viewer
* node items.
*/
struct GeoViewerItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
using ItemT = NodeGeometryViewerItem;
static StructRNA **item_srna;
static int node_type;
static constexpr StringRefNull node_idname = "GeometryNodeViewer";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
struct operator_idnames {
static constexpr StringRefNull add_item = "NODE_OT_geometry_nodes_viewer_item_add";
static constexpr StringRefNull remove_item = "NODE_OT_geometry_nodes_viewer_item_remove";
static constexpr StringRefNull move_item = "NODE_OT_geometry_nodes_viewer_item_move";
};
struct ui_idnames {
static constexpr StringRefNull list = "NODE_UL_geometry_nodes_viewer_items";
};
struct rna_names {
static constexpr StringRefNull items = "viewer_items";
static constexpr StringRefNull active_index = "active_index";
};
static socket_items::SocketItemsRef<NodeGeometryViewerItem> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryViewer *>(node.storage);
return {&storage->items, &storage->items_num, &storage->active_index};
}
static void copy_item(const NodeGeometryViewerItem &src, NodeGeometryViewerItem &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(NodeGeometryViewerItem *item)
{
MEM_SAFE_DELETE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const NodeGeometryViewerItem &item)
{
return item.socket_type;
}
static char **get_name(NodeGeometryViewerItem &item)
{
return &item.name;
}
static void init_with_socket_type_and_name(bNode &node,
NodeGeometryViewerItem &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryViewer *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<GeoViewerItemsAccessor>(node, item, name);
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type)
{
return bke::node_tree_type_supports_socket_type_static(ntree_type, socket_type);
}
static std::string socket_identifier_for_item(const NodeGeometryViewerItem &item)
{
/* These special cases exist for compatibility with older Blender versions when the viewer did
* not have a dynamic number of inputs yet. */
if (item.identifier == 0 && item.socket_type == SOCK_GEOMETRY) {
return "Geometry";
}
if (item.identifier == 1 && item.socket_type != SOCK_GEOMETRY) {
return "Value";
}
return "Item_" + std::to_string(item.identifier);
}
};
void geo_viewer_node_log(const bNode &node,
const Span<bke::SocketValueVariant *> input_values,
eval_log::ViewerNodeLog &r_log);
} // namespace blender::nodes

View File

@@ -0,0 +1,176 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cstring>
#include "MEM_guardedalloc.h"
#include "NOD_geometry.hh"
#include "BKE_context.hh"
#include "BKE_layer.hh"
#include "BKE_node.hh"
#include "BKE_object.hh"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "DNA_space_types.h"
#include "RNA_prototypes.hh"
#include "UI_resources.hh"
#include "BLT_translation.hh"
#include "node_common.h"
namespace blender {
bke::bNodeTreeType *ntreeType_Geometry;
static void geometry_node_tree_get_from_context(const bContext *C,
bke::bNodeTreeType * /*treetype*/,
bNodeTree **r_ntree,
ID **r_id,
ID **r_from)
{
const SpaceNode *snode = CTX_wm_space_node(C);
if (snode->node_tree_sub_type == SNODE_GEOMETRY_TOOL) {
if (snode->selected_node_group && snode->selected_node_group->type == NTREE_GEOMETRY) {
*r_ntree = snode->selected_node_group;
return;
}
*r_ntree = nullptr;
return;
}
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
BKE_view_layer_synced_ensure(*bmain, scene, view_layer);
Object *ob = BKE_view_layer_active_object_get(view_layer);
if (ob == nullptr) {
return;
}
const ModifierData *md = BKE_object_active_modifier(ob);
if (md == nullptr) {
return;
}
if (md->type == eModifierType_Nodes) {
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
if (nmd->node_group != nullptr && !ID_MISSING(nmd->node_group)) {
*r_from = &ob->id;
*r_id = &ob->id;
*r_ntree = nmd->node_group;
}
}
}
static void geometry_node_tree_update(bNodeTree *ntree)
{
bke::node_tree_set_output(*ntree);
/* Needed to give correct types to reroutes. */
ntree_update_reroute_nodes(ntree);
}
static void foreach_nodeclass(void *calldata, bke::bNodeClassCallback func)
{
func(calldata, NODE_CLASS_INPUT, N_("Input"));
func(calldata, NODE_CLASS_GEOMETRY, N_("Geometry"));
func(calldata, NODE_CLASS_ATTRIBUTE, N_("Attribute"));
func(calldata, NODE_CLASS_OP_COLOR, N_("Color"));
func(calldata, NODE_CLASS_OP_VECTOR, N_("Vector"));
func(calldata, NODE_CLASS_CONVERTER, N_("Converter"));
func(calldata, NODE_CLASS_LAYOUT, N_("Layout"));
}
static bool geometry_node_tree_validate_link(eNodeSocketDatatype type_a,
eNodeSocketDatatype type_b)
{
/* Geometry, string, object, material, texture and collection sockets can only be connected to
* themselves. The other types can be converted between each other. */
if (ELEM(type_a, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT) &&
ELEM(type_b, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT))
{
return true;
}
if (ELEM(type_a, SOCK_FLOAT, SOCK_VECTOR) && type_b == SOCK_ROTATION) {
/* Floats and vectors implicitly convert to rotations. */
return true;
}
/* Support implicit conversions between matrices and rotations. */
if (type_a == SOCK_MATRIX && type_b == SOCK_ROTATION) {
return true;
}
if (type_a == SOCK_ROTATION && type_b == SOCK_MATRIX) {
return true;
}
if (type_a == SOCK_ROTATION && type_b == SOCK_VECTOR) {
/* Rotations implicitly convert to vectors. */
return true;
}
return type_a == type_b;
}
static bool geometry_node_tree_socket_type_valid(bke::bNodeTreeType * /*treetype*/,
bke::bNodeSocketType *socket_type)
{
return bke::node_is_static_socket_type(*socket_type) &&
(ELEM(socket_type->type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_INT,
SOCK_STRING,
SOCK_OBJECT,
SOCK_GEOMETRY,
SOCK_COLLECTION,
SOCK_IMAGE,
SOCK_MATERIAL,
SOCK_MENU) ||
ELEM(socket_type->type, SOCK_BUNDLE, SOCK_CLOSURE, SOCK_FONT, SOCK_SOUND));
}
void register_node_tree_type_geo()
{
bke::bNodeTreeType *tt = ntreeType_Geometry = MEM_new<bke::bNodeTreeType>(__func__);
tt->type = NTREE_GEOMETRY;
tt->idname = "GeometryNodeTree"_ustr;
tt->group_idname = "GeometryNodeGroup"_ustr;
tt->ui_name = N_("Geometry Node Editor");
tt->ui_icon = ICON_GEOMETRY_NODES;
tt->ui_description = N_("Advanced geometry editing and tools creation using nodes");
tt->asset_catalog_path_prefix = "Geometry Nodes";
tt->rna_ext.srna = RNA_GeometryNodeTree;
tt->update = geometry_node_tree_update;
tt->get_from_context = geometry_node_tree_get_from_context;
tt->foreach_nodeclass = foreach_nodeclass;
tt->valid_socket_type = geometry_node_tree_socket_type_valid;
tt->validate_link = geometry_node_tree_validate_link;
bke::node_tree_type_add(*tt);
}
bool is_layer_selection_field(const bNodeTreeInterfaceSocket &socket)
{
const bke::bNodeSocketType *typeinfo = socket.socket_typeinfo();
BLI_assert(typeinfo != nullptr);
if (typeinfo->type != SOCK_BOOLEAN) {
return false;
}
return (socket.flag & NODE_INTERFACE_SOCKET_LAYER_SELECTION) != 0;
}
} // namespace blender

View File

@@ -0,0 +1,167 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <optional>
#include "BLI_string.h"
#include "node_geometry_util.hh"
#include "node_util.hh"
#include "DNA_space_types.h"
#include "DNA_userdef_types.h"
#include "BKE_node.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
namespace blender {
namespace nodes {
bool check_tool_context_and_error(GeoNodeExecParams &params)
{
if (!params.user_data()->call_data->operator_data) {
params.error_message_add(NodeWarningType::Error, TIP_("Node must be run as tool"));
params.set_default_remaining_outputs();
return false;
}
return true;
}
void search_link_ops_for_tool_node(GatherLinkSearchOpParams &params)
{
if (params.space_node().node_tree_sub_type == SNODE_GEOMETRY_TOOL) {
search_link_ops_for_basic_node(params);
}
}
void node_geo_sdf_grid_error_not_levelset(GeoNodeExecParams &params)
{
params.error_message_add(
NodeWarningType::Error,
"Input grid is not a valid level set. Use a signed distance field grid as input");
params.set_default_remaining_outputs();
}
namespace enums {
const EnumPropertyItem *attribute_type_type_with_socket_fn(bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free)
{
*r_free = true;
return enum_items_filter(
rna_enum_attribute_type_items, [](const EnumPropertyItem &item) -> bool {
return generic_attribute_type_supported(item) &&
!ELEM(item.value, CD_PROP_INT8, CD_PROP_BYTE_COLOR, CD_PROP_FLOAT2, CD_PROP_FLOAT4);
});
}
bool generic_attribute_type_supported(const EnumPropertyItem &item)
{
return ELEM(item.value,
CD_PROP_FLOAT,
CD_PROP_FLOAT2,
CD_PROP_FLOAT3,
CD_PROP_FLOAT4,
CD_PROP_COLOR,
CD_PROP_BOOL,
CD_PROP_INT8,
CD_PROP_INT32,
CD_PROP_BYTE_COLOR,
CD_PROP_QUATERNION,
CD_PROP_FLOAT4X4);
}
} // namespace enums
const EnumPropertyItem *grid_data_type_socket_items_filter_fn(bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free)
{
*r_free = true;
return enum_items_filter(
rna_enum_volume_grid_data_type_items, [](const EnumPropertyItem &item) -> bool {
return bke::grid_type_to_socket_type(VolumeGridType(item.value)).has_value();
});
}
const EnumPropertyItem *grid_socket_type_items_filter_fn(bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free)
{
*r_free = true;
return enum_items_filter(rna_enum_node_socket_data_type_items,
[](const EnumPropertyItem &item) -> bool {
return socket_type_supports_grids(eNodeSocketDatatype(item.value));
});
}
void node_geo_exec_with_missing_openvdb(GeoNodeExecParams &params)
{
params.set_default_remaining_outputs();
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without OpenVDB"));
}
void node_geo_exec_with_too_old_openvdb(GeoNodeExecParams &params)
{
params.set_default_remaining_outputs();
params.error_message_add(NodeWarningType::Error, TIP_("Disabled, OpenVDB version is too old"));
}
} // namespace nodes
bool geo_node_poll_default(const bke::bNodeType * /*ntype*/,
const bNodeTree *ntree,
const char **r_disabled_hint)
{
if (!STREQ(ntree->idname, "GeometryNodeTree")) {
*r_disabled_hint = RPT_("Not a geometry node tree");
return false;
}
return true;
}
void geo_node_type_base(bke::bNodeType *ntype,
UString idname,
const std::optional<int16_t> legacy_type)
{
bke::node_type_base(*ntype, idname, legacy_type);
ntype->poll = geo_node_poll_default;
ntype->insert_link = node_insert_link_default;
ntype->gather_link_search_ops = nodes::search_link_ops_for_basic_node;
}
static bool geo_cmp_node_poll_default(const bke::bNodeType * /*ntype*/,
const bNodeTree *ntree,
const char **r_disabled_hint)
{
if (!STR_ELEM(ntree->idname, "GeometryNodeTree", "CompositorNodeTree")) {
*r_disabled_hint = RPT_("Not a geometry or compositor node tree");
return false;
}
return true;
}
void geo_cmp_node_type_base(bke::bNodeType *ntype,
UString idname,
const std::optional<int16_t> legacy_type)
{
bke::node_type_base(*ntype, idname, legacy_type);
ntype->poll = geo_cmp_node_poll_default;
ntype->insert_link = node_insert_link_default;
ntype->gather_link_search_ops = nodes::search_link_ops_for_basic_node;
}
} // namespace blender

View File

@@ -0,0 +1,86 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <optional>
#include "MEM_guardedalloc.h" // IWYU pragma: export
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh" // IWYU pragma: export
#include "BKE_node_socket_value.hh" // IWYU pragma: export
#include "NOD_geometry_exec.hh" // IWYU pragma: export
#include "NOD_register.hh" // IWYU pragma: export
#include "NOD_socket_declarations.hh" // IWYU pragma: export
#include "NOD_socket_declarations_geometry.hh" // IWYU pragma: export
#include "node_util.hh" // IWYU pragma: export
namespace blender {
namespace bke {
struct BVHTreeFromMesh;
}
namespace nodes {
class GatherAddNodeSearchParams;
class GatherLinkSearchOpParams;
} // namespace nodes
void geo_node_type_base(bke::bNodeType *ntype,
UString idname,
std::optional<int16_t> legacy_type = std::nullopt);
bool geo_node_poll_default(const bke::bNodeType *ntype,
const bNodeTree *ntree,
const char **r_disabled_hint);
/* Same as geo_node_type_base but allows node use in the compositor by allowing compositor node
* trees in the poll function. */
void geo_cmp_node_type_base(bke::bNodeType *ntype,
UString idname,
std::optional<int16_t> legacy_type = std::nullopt);
namespace nodes {
bool check_tool_context_and_error(GeoNodeExecParams &params);
void search_link_ops_for_tool_node(GatherLinkSearchOpParams &params);
void node_geo_sdf_grid_error_not_levelset(GeoNodeExecParams &params);
void get_closest_in_bvhtree(bke::BVHTreeFromMesh &tree_data,
const VArray<float3> &positions,
const IndexMask &mask,
MutableSpan<int> r_indices,
MutableSpan<float> r_distances_sq,
MutableSpan<float3> r_positions);
namespace enums {
const EnumPropertyItem *attribute_type_type_with_socket_fn(bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free);
bool generic_attribute_type_supported(const EnumPropertyItem &item);
} // namespace enums
const EnumPropertyItem *grid_data_type_socket_items_filter_fn(bContext *C,
PointerRNA *ptr,
PropertyRNA *prop,
bool *r_free);
const EnumPropertyItem *grid_socket_type_items_filter_fn(bContext *C,
PointerRNA *ptr,
PropertyRNA *prop,
bool *r_free);
void node_geo_exec_with_missing_openvdb(GeoNodeExecParams &params);
void node_geo_exec_with_too_old_openvdb(GeoNodeExecParams &params);
void draw_data_blocks(const bContext *C, ui::Layout &layout, PointerRNA &bake_rna);
} // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,452 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute_math.hh"
#include "BLI_array.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_virtual_array.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_enum_types.hh"
#include "node_geometry_util.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_geo_accumulate_field_cc {
NODE_STORAGE_FUNCS(NodeAccumulateField)
static void node_declare(NodeDeclarationBuilder &b)
{
const bNode *node = b.node_or_null();
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node_storage(*node).data_type);
BaseSocketDeclarationBuilder *value_declaration = nullptr;
switch (data_type) {
case CD_PROP_FLOAT3:
value_declaration =
&b.add_input<decl::Vector>("Value"_ustr).default_value({1.0f, 1.0f, 1.0f});
break;
case CD_PROP_FLOAT:
value_declaration = &b.add_input<decl::Float>("Value"_ustr).default_value(1.0f);
break;
case CD_PROP_INT32:
value_declaration = &b.add_input<decl::Int>("Value"_ustr).default_value(1);
break;
case CD_PROP_FLOAT4X4:
value_declaration = &b.add_input<decl::Matrix>("Value"_ustr);
break;
default:
BLI_assert_unreachable();
break;
}
value_declaration->structure_type(StructureType::Field)
.description("The values to be accumulated");
}
b.add_input<decl::Int>("Group ID"_ustr, "Group Index"_ustr)
.structure_type(StructureType::Field)
.hide_value()
.description("An index used to group values together for multiple separate accumulations");
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node_storage(*node).data_type);
b.add_output(data_type, "Leading"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description(
"The running total of values in the corresponding group, starting at the first value");
b.add_output(data_type, "Trailing"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("The running total of values in the corresponding group, starting at zero");
b.add_output(data_type, "Total"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("The total of all of the values in the corresponding group");
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
layout.prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeAccumulateField *data = MEM_new<NodeAccumulateField>(__func__);
data->data_type = CD_PROP_FLOAT;
data->domain = int16_t(AttrDomain::Point);
node->storage = data;
}
enum class AccumulationMode { Leading = 0, Trailing = 1 };
static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT:
return CD_PROP_FLOAT;
case SOCK_BOOLEAN:
case SOCK_INT:
return CD_PROP_INT32;
case SOCK_VECTOR:
case SOCK_RGBA:
case SOCK_ROTATION:
return CD_PROP_FLOAT3;
case SOCK_MATRIX:
return CD_PROP_FLOAT4X4;
default:
return std::nullopt;
}
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const NodeDeclaration &declaration = *params.node_type().static_declaration;
search_link_ops_for_declarations(params, declaration.inputs);
const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
if (!type) {
return;
}
if (params.in_out() == SOCK_OUT) {
params.add_item(
IFACE_("Leading"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeAccumulateField"_ustr);
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Leading"_ustr);
},
0);
params.add_item(
IFACE_("Trailing"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeAccumulateField"_ustr);
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Trailing"_ustr);
},
-1);
params.add_item(
IFACE_("Total"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeAccumulateField"_ustr);
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Total"_ustr);
},
-2);
}
else {
params.add_item(
IFACE_("Value"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeAccumulateField"_ustr);
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Value"_ustr);
},
0);
}
}
template<typename T> struct AccumulationInfo {
static inline const T initial_value = []() {
if constexpr (std::is_same_v<T, float4x4>) {
return float4x4::identity();
}
else {
return T();
}
}();
static T accumulate(const T &a, const T &b)
{
if constexpr (std::is_same_v<T, float4x4>) {
return a * b;
}
else {
return a + b;
}
}
};
class AccumulateFieldInput final : public bke::GeometryFieldInput {
private:
GField input_;
Field<int> group_index_;
AttrDomain source_domain_;
AccumulationMode accumulation_mode_;
public:
AccumulateFieldInput(const AttrDomain source_domain,
GField input,
Field<int> group_index,
AccumulationMode accumulation_mode)
: bke::GeometryFieldInput(input.cpp_type(), "Accumulation"),
input_(std::move(input)),
group_index_(std::move(group_index)),
source_domain_(source_domain),
accumulation_mode_(accumulation_mode)
{
}
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
const IndexMask & /*mask*/) const final
{
const AttributeAccessor attributes = *context.attributes();
const int64_t domain_size = attributes.domain_size(source_domain_);
if (domain_size == 0) {
return {};
}
const bke::GeometryFieldContext source_context{context, source_domain_};
fn::FieldEvaluator evaluator{source_context, domain_size};
evaluator.add(input_);
evaluator.add(group_index_);
evaluator.evaluate();
const GVArray g_values = evaluator.get_evaluated(0);
const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
GVArray g_output;
g_values.type().to_static_type<int, float, float3, float4x4>([&]<typename T>() {
Array<T> outputs(domain_size);
const VArray<T> values = g_values.typed<T>();
if (group_indices.is_single()) {
T accumulation = AccumulationInfo<T>::initial_value;
if (accumulation_mode_ == AccumulationMode::Leading) {
for (const int i : values.index_range()) {
accumulation = AccumulationInfo<T>::accumulate(accumulation, values[i]);
outputs[i] = accumulation;
}
}
else {
for (const int i : values.index_range()) {
outputs[i] = accumulation;
accumulation = AccumulationInfo<T>::accumulate(accumulation, values[i]);
}
}
}
else {
Map<int, T> accumulations;
if (accumulation_mode_ == AccumulationMode::Leading) {
for (const int i : values.index_range()) {
T &accumulation_value = accumulations.lookup_or_add(
group_indices[i], AccumulationInfo<T>::initial_value);
accumulation_value = AccumulationInfo<T>::accumulate(accumulation_value, values[i]);
outputs[i] = accumulation_value;
}
}
else {
for (const int i : values.index_range()) {
T &accumulation_value = accumulations.lookup_or_add(
group_indices[i], AccumulationInfo<T>::initial_value);
outputs[i] = accumulation_value;
accumulation_value = AccumulationInfo<T>::accumulate(accumulation_value, values[i]);
}
}
}
g_output = VArray<T>::from_container(std::move(outputs));
});
return attributes.adapt_domain(std::move(g_output), source_domain_, context.domain());
}
void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const override
{
fn(input_);
fn(group_index_);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(input_));
hash.add(deep_hash_cache.ensure(group_index_));
hash.add(source_domain_);
hash.add(accumulation_mode_);
}
std::optional<AttrDomain> preferred_domain(
const GeometryComponent & /*component*/) const override
{
return source_domain_;
}
};
class TotalFieldInput final : public bke::GeometryFieldInput {
private:
GField input_;
Field<int> group_index_;
AttrDomain source_domain_;
public:
TotalFieldInput(const AttrDomain source_domain, GField input, Field<int> group_index)
: bke::GeometryFieldInput(input.cpp_type(), "Total Value"),
input_(std::move(input)),
group_index_(std::move(group_index)),
source_domain_(source_domain)
{
}
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
const IndexMask & /*mask*/) const final
{
const AttributeAccessor attributes = *context.attributes();
const int64_t domain_size = attributes.domain_size(source_domain_);
if (domain_size == 0) {
return {};
}
const bke::GeometryFieldContext source_context{context, source_domain_};
fn::FieldEvaluator evaluator{source_context, domain_size};
evaluator.add(input_);
evaluator.add(group_index_);
evaluator.evaluate();
const GVArray g_values = evaluator.get_evaluated(0);
const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
GVArray g_outputs;
g_values.type().to_static_type<int, float, float3, float4x4>([&]<typename T>() {
const VArray<T> values = g_values.typed<T>();
if (group_indices.is_single()) {
T accumulation = AccumulationInfo<T>::initial_value;
for (const int i : values.index_range()) {
accumulation = AccumulationInfo<T>::accumulate(accumulation, values[i]);
}
g_outputs = VArray<T>::from_single(accumulation, domain_size);
}
else {
Map<int, T> accumulations;
for (const int i : values.index_range()) {
T &value = accumulations.lookup_or_add(group_indices[i],
AccumulationInfo<T>::initial_value);
value = AccumulationInfo<T>::accumulate(value, values[i]);
}
Array<T> outputs(domain_size);
for (const int i : values.index_range()) {
outputs[i] = accumulations.lookup(group_indices[i]);
}
g_outputs = VArray<T>::from_container(std::move(outputs));
}
});
return attributes.adapt_domain(std::move(g_outputs), source_domain_, context.domain());
}
void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const override
{
fn(input_);
fn(group_index_);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(input_));
hash.add(deep_hash_cache.ensure(group_index_));
hash.add(source_domain_);
}
std::optional<AttrDomain> preferred_domain(
const GeometryComponent & /*component*/) const override
{
return source_domain_;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeAccumulateField &storage = node_storage(params.node());
const AttrDomain source_domain = AttrDomain(storage.domain);
const Field<int> group_index_field = params.extract_input<Field<int>>("Group Index"_ustr);
const GField input_field = params.extract_input<GField>("Value"_ustr);
if (params.output_is_required("Leading"_ustr)) {
params.set_output<GField>(
"Leading"_ustr,
GField::from_input<AccumulateFieldInput>(
source_domain, input_field, group_index_field, AccumulationMode::Leading));
}
if (params.output_is_required("Trailing"_ustr)) {
params.set_output<GField>(
"Trailing"_ustr,
GField::from_input<AccumulateFieldInput>(
source_domain, input_field, group_index_field, AccumulationMode::Trailing));
}
if (params.output_is_required("Total"_ustr)) {
params.set_output<GField>(
"Total"_ustr,
GField::from_input<TotalFieldInput>(source_domain, input_field, group_index_field));
}
}
static void node_rna(StructRNA *srna)
{
static EnumPropertyItem items[] = {
{CD_PROP_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, "Float", "Add floating point values"},
{CD_PROP_INT32, "INT", ICON_NODE_SOCKET_INT, "Integer", "Add integer values"},
{CD_PROP_FLOAT3, "FLOAT_VECTOR", ICON_NODE_SOCKET_VECTOR, "Vector", "Add 3D vector values"},
{CD_PROP_FLOAT4X4,
"TRANSFORM",
ICON_NODE_SOCKET_MATRIX,
"Transform",
"Multiply transformation matrices"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"Type of data that is accumulated",
items,
NOD_storage_enum_accessors(data_type),
CD_PROP_FLOAT);
RNA_def_node_enum(srna,
"domain",
"Domain",
"",
rna_enum_attribute_domain_items,
NOD_storage_enum_accessors(domain),
int(AttrDomain::Point),
nullptr,
true);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeAccumulateField"_ustr, GEO_NODE_ACCUMULATE_FIELD);
ntype.ui_name = "Accumulate Field";
ntype.ui_description =
"Add the values of an evaluated field together and output the running total for each "
"element";
ntype.enum_name_legacy = "ACCUMULATE_FIELD";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.geometry_node_execute = node_geo_exec;
ntype.initfunc = node_init;
ntype.draw_buttons = node_layout;
ntype.declare = node_declare;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_type_storage(
ntype, "NodeAccumulateField", node_free_standard_storage, node_copy_standard_storage);
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_accumulate_field_cc

View File

@@ -0,0 +1,339 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_geo_capture_attribute.hh"
#include "NOD_socket_items_blend.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_enum_types.hh"
#include "BLO_read_write.hh"
#include "BKE_library.hh"
#include "BKE_screen.hh"
#include "GEO_foreach_geometry.hh"
#include "node_geometry_util.hh"
namespace blender {
namespace nodes::node_geo_attribute_capture_cc {
NODE_STORAGE_FUNCS(NodeGeometryAttributeCapture)
static void node_declare(NodeDeclarationBuilder &b)
{
const bNodeTree *tree = b.tree_or_null();
const bNode *node = b.node_or_null();
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
b.add_input<decl::Geometry>("Geometry"_ustr)
.description(
"Geometry to evaluate the given fields and store the resulting attributes on. All "
"geometry types except volumes are supported");
b.add_output<decl::Geometry>("Geometry"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field();
b.add_output<decl::Bool>("Selection"_ustr).anonymous_attribute_output().align_with_previous();
if (node != nullptr) {
const NodeGeometryAttributeCapture &storage = node_storage(*node);
for (const NodeGeometryAttributeCaptureItem &item :
Span(storage.capture_items, storage.capture_items_num))
{
const UString name(item.name);
const eCustomDataType data_type = eCustomDataType(item.data_type);
const UString input_identifier(
CaptureAttributeItemsAccessor::input_socket_identifier_for_item(item));
const UString output_identifier(
CaptureAttributeItemsAccessor::output_socket_identifier_for_item(item));
b.add_input(data_type, name, input_identifier)
.evaluated_geometry_field()
.socket_name_ptr(&tree->id, *CaptureAttributeItemsAccessor::item_srna, &item, "name");
b.add_output(data_type, name, output_identifier)
.anonymous_attribute_output()
.align_with_previous();
}
}
b.add_input<decl::Extend>(""_ustr, "__extend__"_ustr)
.structure_type(StructureType::Field)
.custom_draw(socket_items::ui::draw_extend_socket_fn<CaptureAttributeItemsAccessor>());
b.add_output<decl::Extend>(""_ustr, "__extend__"_ustr)
.structure_type(StructureType::Field)
.align_with_previous();
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
layout.prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryAttributeCapture *data = MEM_new<NodeGeometryAttributeCapture>(__func__);
data->domain = int8_t(AttrDomain::Point);
node->storage = data;
}
static void node_layout_ex(ui::Layout &layout, bContext *C, PointerRNA *ptr)
{
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode &node = *static_cast<bNode *>(ptr->data);
layout.prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
if (ui::Layout *panel = layout.panel(
C, "capture_attribute_items", false, IFACE_("Capture Items")))
{
socket_items::ui::draw_items_list_with_operators<CaptureAttributeItemsAccessor>(
C, panel, tree, node);
socket_items::ui::draw_active_item_props<CaptureAttributeItemsAccessor>(
tree, node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "data_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
});
}
}
static void node_operators()
{
socket_items::ops::make_common_operators<CaptureAttributeItemsAccessor>();
}
static void clean_unused_attributes(const AttributeFilter &attribute_filter,
const VectorSet<std::string> &keep,
GeometryComponent &component)
{
std::optional<MutableAttributeAccessor> attributes = component.attributes_for_write();
if (!attributes.has_value()) {
return;
}
Vector<std::string> unused_ids;
attributes->foreach_attribute([&](const bke::AttributeIter &iter) {
if (!bke::attribute_name_is_anonymous(iter.name)) {
return;
}
if (keep.contains(iter.name)) {
return;
}
if (!attribute_filter.allow_skip(iter.name)) {
return;
}
unused_ids.append(iter.name);
});
for (const std::string &unused_id : unused_ids) {
attributes->remove(unused_id);
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"_ustr);
if (!params.output_is_required("Geometry"_ustr)) {
params.error_message_add(
NodeWarningType::Info,
TIP_("The attribute output cannot be used without the geometry output"));
params.set_default_remaining_outputs();
return;
}
const NodeGeometryAttributeCapture &storage = node_storage(params.node());
const AttrDomain domain = AttrDomain(storage.domain);
const Field<bool> selection = params.extract_input<Field<bool>>("Selection"_ustr);
Vector<GField> fields;
VectorSet<std::string> out_attribute_names;
for (const NodeGeometryAttributeCaptureItem &item :
Span{storage.capture_items, storage.capture_items_num})
{
const std::string input_identifier =
CaptureAttributeItemsAccessor::input_socket_identifier_for_item(item);
const std::string output_identifier =
CaptureAttributeItemsAccessor::output_socket_identifier_for_item(item);
std::optional<std::string> attribute_id = params.get_output_anonymous_attribute_id_if_needed(
UString(output_identifier));
if (!attribute_id) {
continue;
}
fields.append(params.extract_input<GField>(UString(input_identifier)));
out_attribute_names.add_new(std::move(*attribute_id));
}
{
std::optional<std::string> selection_attribute_id =
params.get_output_anonymous_attribute_id_if_needed("Selection"_ustr);
if (selection_attribute_id) {
fields.append(selection);
out_attribute_names.add_new(std::move(*selection_attribute_id));
}
}
if (fields.is_empty()) {
params.set_output("Geometry"_ustr, geometry_set);
params.set_default_remaining_outputs();
return;
}
const Array<StringRef> out_attribute_name_refs = out_attribute_names.as_span();
const auto capture_on = [&](GeometryComponent &component) {
bke::try_capture_fields_on_geometry(
component, out_attribute_name_refs, domain, selection, fields);
/* Changing of the anonymous attributes may require removing attributes that are no longer
* needed. */
clean_unused_attributes(
params.get_attribute_filter("Geometry"_ustr), out_attribute_names, component);
};
/* Run on the instances component separately to only affect the top level of instances. */
if (domain == AttrDomain::Instance) {
if (geometry_set.has_instances()) {
capture_on(geometry_set.get_component_for_write(GeometryComponent::Type::Instance));
}
}
else {
static const Array<GeometryComponent::Type> types = {GeometryComponent::Type::Mesh,
GeometryComponent::Type::PointCloud,
GeometryComponent::Type::Curve,
GeometryComponent::Type::GreasePencil};
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
for (const GeometryComponent::Type type : types) {
if (geometry_set.has(type)) {
capture_on(geometry_set.get_component_for_write(type));
}
}
});
}
params.set_output("Geometry"_ustr, geometry_set);
}
static bool node_insert_link(bke::NodeInsertLinkParams &params)
{
return socket_items::try_add_item_via_any_extend_socket<CaptureAttributeItemsAccessor>(
params.ntree, params.node, params.node, params.link);
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<CaptureAttributeItemsAccessor>(*node);
MEM_delete(reinterpret_cast<NodeGeometryAttributeCapture *>(node->storage));
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeGeometryAttributeCapture &src_storage = node_storage(*src_node);
NodeGeometryAttributeCapture *dst_storage = MEM_new<NodeGeometryAttributeCapture>(
__func__, dna::shallow_copy(src_storage));
dst_node->storage = dst_storage;
socket_items::copy_array<CaptureAttributeItemsAccessor>(*src_node, *dst_node);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const eNodeSocketDatatype type = params.other_socket().type;
if (type == SOCK_GEOMETRY) {
params.add_item(IFACE_("Geometry"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeCaptureAttribute"_ustr);
params.connect_available_socket(node, "Geometry"_ustr);
});
}
if (!CaptureAttributeItemsAccessor::supports_socket_type(type, params.node_tree().type)) {
return;
}
params.add_item(IFACE_("Value"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeCaptureAttribute"_ustr);
const auto *item =
socket_items::add_item_with_socket_type_and_name<CaptureAttributeItemsAccessor>(
params.node_tree, node, type, params.socket.name);
const std::string identifier =
params.socket.in_out == SOCK_IN ?
CaptureAttributeItemsAccessor::output_socket_identifier_for_item(*item) :
CaptureAttributeItemsAccessor::input_socket_identifier_for_item(*item);
params.update_and_connect_available_socket_by_identifier(node, UString(identifier));
});
}
static const bNodeSocket *node_internally_linked_input(const bNodeTree & /*tree*/,
const bNode &node,
const bNodeSocket &output_socket)
{
return &node.input_socket(output_socket.index());
}
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
{
socket_items::blend_write<CaptureAttributeItemsAccessor>(&writer, node);
}
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
{
socket_items::blend_read_data<CaptureAttributeItemsAccessor>(&reader, node);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCaptureAttribute"_ustr, GEO_NODE_CAPTURE_ATTRIBUTE);
ntype.ui_name = "Capture Attribute";
ntype.ui_description =
"Store the result of a field on a geometry and output the data as a node socket. Allows "
"remembering or interpolating data as the geometry changes, such as positions before "
"deformation";
ntype.enum_name_legacy = "CAPTURE_ATTRIBUTE";
ntype.nclass = NODE_CLASS_ATTRIBUTE;
bke::node_type_storage(
ntype, "NodeGeometryAttributeCapture", node_free_storage, node_copy_storage);
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.insert_link = node_insert_link;
ntype.draw_buttons = node_layout;
ntype.draw_buttons_ex = node_layout_ex;
ntype.register_operators = node_operators;
ntype.gather_link_search_ops = node_gather_link_searches;
ntype.internally_linked_input = node_internally_linked_input;
ntype.blend_write_storage_content = node_blend_write;
ntype.blend_data_read_storage_content = node_blend_read;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace nodes::node_geo_attribute_capture_cc
namespace nodes {
StructRNA **CaptureAttributeItemsAccessor::item_srna = &RNA_NodeGeometryCaptureAttributeItem;
void CaptureAttributeItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
writer->write_string(item.name);
}
void CaptureAttributeItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
} // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,209 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "RNA_enum_types.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_attribute_domain_size_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry"_ustr)
.description(
"Geometry to get the domain sizes of. Only the root geometry is considered, not nested "
"instances");
auto &total_points = b.add_output<decl::Int>("Point Count"_ustr)
.make_available([](bNode &node) {
node.custom1 = int16_t(GeometryComponent::Type::Mesh);
})
.available(false);
auto &total_edges = b.add_output<decl::Int>("Edge Count"_ustr)
.make_available([](bNode &node) {
node.custom1 = int16_t(GeometryComponent::Type::Mesh);
})
.available(false);
auto &total_faces = b.add_output<decl::Int>("Face Count"_ustr)
.make_available([](bNode &node) {
node.custom1 = int16_t(GeometryComponent::Type::Mesh);
})
.available(false);
auto &total_corners = b.add_output<decl::Int>("Face Corner Count"_ustr)
.make_available([](bNode &node) {
node.custom1 = int16_t(GeometryComponent::Type::Mesh);
})
.available(false);
auto &total_curves = b.add_output<decl::Int>("Spline Count"_ustr)
.make_available([](bNode &node) {
node.custom1 = int16_t(GeometryComponent::Type::Curve);
})
.available(false);
auto &total_instances = b.add_output<decl::Int>("Instance Count"_ustr)
.make_available([](bNode &node) {
node.custom1 = int16_t(GeometryComponent::Type::Instance);
})
.available(false);
auto &total_layers = b.add_output<decl::Int>("Layer Count"_ustr)
.make_available([](bNode &node) {
node.custom1 = int16_t(GeometryComponent::Type::GreasePencil);
})
.available(false);
const bNode *node = b.node_or_null();
if (node != nullptr) {
switch (GeometryComponent::Type(node->custom1)) {
case GeometryComponent::Type::Mesh:
total_points.available(true);
total_edges.available(true);
total_faces.available(true);
total_corners.available(true);
break;
case GeometryComponent::Type::Curve:
total_points.available(true);
total_curves.available(true);
break;
case GeometryComponent::Type::PointCloud:
total_points.available(true);
break;
case GeometryComponent::Type::Instance:
total_instances.available(true);
break;
case GeometryComponent::Type::GreasePencil:
total_layers.available(true);
break;
default:
break;
}
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "component", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = int16_t(GeometryComponent::Type::Mesh);
}
static void node_geo_exec(GeoNodeExecParams params)
{
const GeometryComponent::Type component = GeometryComponent::Type(params.node().custom1);
const GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"_ustr);
switch (component) {
case GeometryComponent::Type::Mesh: {
if (const MeshComponent *component = geometry_set.get_component<MeshComponent>()) {
const AttributeAccessor attributes = *component->attributes();
params.set_output("Point Count"_ustr, attributes.domain_size(AttrDomain::Point));
params.set_output("Edge Count"_ustr, attributes.domain_size(AttrDomain::Edge));
params.set_output("Face Count"_ustr, attributes.domain_size(AttrDomain::Face));
params.set_output("Face Corner Count"_ustr, attributes.domain_size(AttrDomain::Corner));
}
else {
params.set_default_remaining_outputs();
}
break;
}
case GeometryComponent::Type::Curve: {
if (const CurveComponent *component = geometry_set.get_component<CurveComponent>()) {
const AttributeAccessor attributes = *component->attributes();
params.set_output("Point Count"_ustr, attributes.domain_size(AttrDomain::Point));
params.set_output("Spline Count"_ustr, attributes.domain_size(AttrDomain::Curve));
}
else {
params.set_default_remaining_outputs();
}
break;
}
case GeometryComponent::Type::PointCloud: {
if (const PointCloudComponent *component = geometry_set.get_component<PointCloudComponent>())
{
const AttributeAccessor attributes = *component->attributes();
params.set_output("Point Count"_ustr, attributes.domain_size(AttrDomain::Point));
}
else {
params.set_default_remaining_outputs();
}
break;
}
case GeometryComponent::Type::Instance: {
if (const InstancesComponent *component = geometry_set.get_component<InstancesComponent>()) {
const AttributeAccessor attributes = *component->attributes();
params.set_output("Instance Count"_ustr, attributes.domain_size(AttrDomain::Instance));
}
else {
params.set_default_remaining_outputs();
}
break;
}
case GeometryComponent::Type::GreasePencil: {
if (const GreasePencilComponent *component =
geometry_set.get_component<GreasePencilComponent>())
{
const AttributeAccessor attributes = *component->attributes();
params.set_output("Layer Count"_ustr, attributes.domain_size(AttrDomain::Layer));
}
else {
params.set_default_remaining_outputs();
}
break;
}
default:
params.set_default_remaining_outputs();
break;
}
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(
srna,
"component",
"Component",
"",
rna_enum_geometry_component_type_items,
NOD_inline_enum_accessors(custom1),
int(bke::GeometryComponent::Type::Mesh),
[](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
*r_free = true;
return enum_items_filter(rna_enum_geometry_component_type_items,
[](const EnumPropertyItem &item) -> bool {
return ELEM(item.value,
int(GeometryComponent::Type::Mesh),
int(GeometryComponent::Type::Curve),
int(GeometryComponent::Type::PointCloud),
int(GeometryComponent::Type::Instance),
int(GeometryComponent::Type::GreasePencil));
});
});
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeAttributeDomainSize"_ustr, GEO_NODE_ATTRIBUTE_DOMAIN_SIZE);
ntype.ui_name = "Domain Size";
ntype.ui_description = "Retrieve the number of elements in a geometry for each attribute domain";
ntype.enum_name_legacy = "ATTRIBUTE_DOMAIN_SIZE";
ntype.nclass = NODE_CLASS_ATTRIBUTE;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.initfunc = node_init;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_attribute_domain_size_cc

View File

@@ -0,0 +1,386 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <algorithm>
#include <numeric>
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "BLI_array_utils.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_enum_types.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_attribute_statistic_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
const bNode *node = b.node_or_null();
b.add_input<decl::Geometry>("Geometry"_ustr).description("Geometry to get the statistics from");
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.evaluated_geometry_field()
.hide_value();
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node->custom1);
b.add_input(data_type, "Attribute"_ustr).hide_value().evaluated_geometry_field();
b.add_output(data_type, "Mean"_ustr);
b.add_output(data_type, CTX_N_(BLT_I18NCONTEXT_ID_NODETREE, "Median"_ustr))
.translation_context(BLT_I18NCONTEXT_ID_NODETREE);
b.add_output(data_type, "Sum"_ustr);
b.add_output(data_type, "Min"_ustr);
b.add_output(data_type, "Max"_ustr);
b.add_output(data_type, "Range"_ustr);
b.add_output(data_type, "Standard Deviation"_ustr);
b.add_output(data_type, "Variance"_ustr);
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
layout.prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = CD_PROP_FLOAT;
node->custom2 = int16_t(AttrDomain::Point);
}
static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT:
case SOCK_BOOLEAN:
case SOCK_INT:
return CD_PROP_FLOAT;
case SOCK_VECTOR:
case SOCK_RGBA:
case SOCK_ROTATION:
return CD_PROP_FLOAT3;
default:
return {};
}
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const bke::bNodeType &node_type = params.node_type();
const NodeDeclaration &declaration = *params.node_type().static_declaration;
search_link_ops_for_declarations(params, declaration.inputs);
const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
if (!type) {
return;
}
if (params.in_out() == SOCK_IN) {
params.add_item(IFACE_("Attribute"), [node_type, type](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
node.custom1 = *type;
params.update_and_connect_available_socket(node, "Attribute"_ustr);
});
}
else {
for (const UString name : {"Mean"_ustr,
"Median"_ustr,
"Sum"_ustr,
"Min"_ustr,
"Max"_ustr,
"Range"_ustr,
"Standard Deviation"_ustr,
"Variance"_ustr})
{
params.add_item(IFACE_(name.ref()), [node_type, name, type](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
node.custom1 = *type;
params.update_and_connect_available_socket(node, name);
});
}
}
}
static float compute_variance(const Span<float> data, const float mean)
{
if (data.size() <= 1) {
return 0.0f;
}
float sum_of_squared_differences = std::accumulate(
data.begin(), data.end(), 0.0f, [mean](float accumulator, float value) {
float difference = mean - value;
return accumulator + difference * difference;
});
return sum_of_squared_differences / data.size();
}
static float median_of_sorted_span(const Span<float> data)
{
if (data.is_empty()) {
return 0.0f;
}
const float median = data[data.size() / 2];
/* For spans of even length, the median is the average of the middle two elements. */
if (data.size() % 2 == 0) {
return (median + data[data.size() / 2 - 1]) * 0.5f;
}
return median;
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"_ustr);
const bNode &node = params.node();
const eCustomDataType data_type = eCustomDataType(node.custom1);
const AttrDomain domain = AttrDomain(node.custom2);
Vector<const GeometryComponent *> components = geometry_set.get_components();
const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"_ustr);
switch (data_type) {
case CD_PROP_FLOAT: {
const Field<float> input_field = params.extract_input<Field<float>>("Attribute"_ustr);
Vector<float> data;
for (const GeometryComponent *component : components) {
const int domain_size = component->attribute_domain_size(domain);
if (domain_size == 0) {
continue;
}
const bke::GeometryFieldContext field_context{*component, domain};
fn::FieldEvaluator data_evaluator{field_context, domain_size};
data_evaluator.add(input_field);
data_evaluator.set_selection(selection_field);
data_evaluator.evaluate();
const VArray<float> component_data = data_evaluator.get_evaluated<float>(0);
const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask();
const int next_data_index = data.size();
data.resize(next_data_index + selection.size());
MutableSpan<float> selected_data = data.as_mutable_span().slice(next_data_index,
selection.size());
array_utils::gather(component_data, selection, selected_data);
}
float mean = 0.0f;
float median = 0.0f;
float sum = 0.0f;
float min = 0.0f;
float max = 0.0f;
float range = 0.0f;
float standard_deviation = 0.0f;
float variance = 0.0f;
const bool sort_required = params.output_is_required("Min"_ustr) ||
params.output_is_required("Max"_ustr) ||
params.output_is_required("Range"_ustr) ||
params.output_is_required("Median"_ustr);
const bool sum_required = params.output_is_required("Sum"_ustr) ||
params.output_is_required("Mean"_ustr);
const bool variance_required = params.output_is_required("Standard Deviation"_ustr) ||
params.output_is_required("Variance"_ustr);
if (data.size() != 0) {
if (sort_required) {
std::ranges::sort(data);
median = median_of_sorted_span(data);
min = data.first();
max = data.last();
range = max - min;
}
if (sum_required || variance_required) {
sum = array_utils::compute_sum<float>(data);
mean = sum / data.size();
if (variance_required) {
variance = compute_variance(data, mean);
standard_deviation = std::sqrt(variance);
}
}
}
if (sum_required) {
params.set_output("Sum"_ustr, sum);
params.set_output("Mean"_ustr, mean);
}
if (sort_required) {
params.set_output("Min"_ustr, min);
params.set_output("Max"_ustr, max);
params.set_output("Range"_ustr, range);
params.set_output("Median"_ustr, median);
}
if (variance_required) {
params.set_output("Standard Deviation"_ustr, standard_deviation);
params.set_output("Variance"_ustr, variance);
}
break;
}
case CD_PROP_FLOAT3: {
const Field<float3> input_field = params.extract_input<Field<float3>>("Attribute"_ustr);
Vector<float3> data;
for (const GeometryComponent *component : components) {
const std::optional<AttributeAccessor> attributes = component->attributes();
if (!attributes.has_value()) {
continue;
}
if (attributes->domain_supported(domain)) {
const bke::GeometryFieldContext field_context{*component, domain};
fn::FieldEvaluator data_evaluator{field_context, attributes->domain_size(domain)};
data_evaluator.add(input_field);
data_evaluator.set_selection(selection_field);
data_evaluator.evaluate();
const VArray<float3> component_data = data_evaluator.get_evaluated<float3>(0);
const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask();
const int next_data_index = data.size();
data.resize(data.size() + selection.size());
MutableSpan<float3> selected_data = data.as_mutable_span().slice(next_data_index,
selection.size());
array_utils::gather(component_data, selection, selected_data);
}
}
float3 median{0};
float3 min{0};
float3 max{0};
float3 range{0};
float3 sum{0};
float3 mean{0};
float3 variance{0};
float3 standard_deviation{0};
const bool sort_required = params.output_is_required("Min"_ustr) ||
params.output_is_required("Max"_ustr) ||
params.output_is_required("Range"_ustr) ||
params.output_is_required("Median"_ustr);
const bool sum_required = params.output_is_required("Sum"_ustr) ||
params.output_is_required("Mean"_ustr);
const bool variance_required = params.output_is_required("Standard Deviation"_ustr) ||
params.output_is_required("Variance"_ustr);
Array<float> data_x;
Array<float> data_y;
Array<float> data_z;
if (sort_required || variance_required) {
data_x.reinitialize(data.size());
data_y.reinitialize(data.size());
data_z.reinitialize(data.size());
for (const int i : data.index_range()) {
data_x[i] = data[i].x;
data_y[i] = data[i].y;
data_z[i] = data[i].z;
}
}
if (data.size() != 0) {
if (sort_required) {
std::ranges::sort(data_x);
std::ranges::sort(data_y);
std::ranges::sort(data_z);
const float x_median = median_of_sorted_span(data_x);
const float y_median = median_of_sorted_span(data_y);
const float z_median = median_of_sorted_span(data_z);
median = float3(x_median, y_median, z_median);
min = float3(data_x.first(), data_y.first(), data_z.first());
max = float3(data_x.last(), data_y.last(), data_z.last());
range = max - min;
}
if (sum_required || variance_required) {
sum = array_utils::compute_sum(data.as_span());
mean = sum / data.size();
if (variance_required) {
const float x_variance = compute_variance(data_x, mean.x);
const float y_variance = compute_variance(data_y, mean.y);
const float z_variance = compute_variance(data_z, mean.z);
variance = float3(x_variance, y_variance, z_variance);
standard_deviation = float3(
std::sqrt(variance.x), std::sqrt(variance.y), std::sqrt(variance.z));
}
}
}
if (sum_required) {
params.set_output("Sum"_ustr, sum);
params.set_output("Mean"_ustr, mean);
}
if (sort_required) {
params.set_output("Min"_ustr, min);
params.set_output("Max"_ustr, max);
params.set_output("Range"_ustr, range);
params.set_output("Median"_ustr, median);
}
if (variance_required) {
params.set_output("Standard Deviation"_ustr, standard_deviation);
params.set_output("Variance"_ustr, variance);
}
break;
}
default:
break;
}
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(
srna,
"data_type",
"Data Type",
"The data type the attribute is converted to before calculating the results",
rna_enum_attribute_type_items,
NOD_inline_enum_accessors(custom1),
CD_PROP_FLOAT,
[](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
*r_free = true;
return enum_items_filter(rna_enum_attribute_type_items, [](const EnumPropertyItem &item) {
return ELEM(item.value, CD_PROP_FLOAT, CD_PROP_FLOAT3);
});
});
RNA_def_node_enum(srna,
"domain",
"Domain",
"Which domain to read the data from",
rna_enum_attribute_domain_items,
NOD_inline_enum_accessors(custom2),
int(AttrDomain::Point),
nullptr,
true);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeAttributeStatistic"_ustr, GEO_NODE_ATTRIBUTE_STATISTIC);
ntype.ui_name = "Attribute Statistic";
ntype.ui_description =
"Calculate statistics about a data set from a field evaluated on a geometry";
ntype.enum_name_legacy = "ATTRIBUTE_STATISTIC";
ntype.nclass = NODE_CLASS_ATTRIBUTE;
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_attribute_statistic_cc

View File

@@ -0,0 +1,822 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fmt/format.h>
#include "NOD_geo_bake.hh"
#include "NOD_node_extra_info.hh"
#include "NOD_socket_items_blend.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "NOD_socket_search_link.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "BLI_path_utils.hh"
#include "BLI_string_utf8.h"
#include "BKE_anonymous_attribute_make.hh"
#include "BKE_bake_geometry_nodes_modifier.hh"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_library.hh"
#include "BKE_main.hh"
#include "BKE_screen.hh"
#include "ED_node.hh"
#include "DNA_modifier_types.h"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "MOD_nodes.hh"
#include "WM_api.hh"
#include "BLO_read_write.hh"
#include "GEO_mix_geometries.hh"
#include "node_geometry_util.hh"
namespace blender {
namespace nodes::node_geo_bake_cc {
namespace bake = bke::bake;
NODE_STORAGE_FUNCS(NodeGeometryBake)
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
const bNodeTree *ntree = b.tree_or_null();
const bNode *node = b.node_or_null();
if (!node) {
return;
}
const NodeGeometryBake &storage = node_storage(*node);
for (const int i : IndexRange(storage.items_num)) {
const NodeGeometryBakeItem &item = storage.items[i];
const eNodeSocketDatatype socket_type = item.socket_type;
const UString name(item.name);
const UString identifier(BakeItemsAccessor::socket_identifier_for_item(item));
auto &input_decl = b.add_input(socket_type, name, identifier)
.socket_name_ptr(
&ntree->id, *BakeItemsAccessor::item_srna, &item, "name");
auto &output_decl = b.add_output(socket_type, name, identifier).align_with_previous();
if (socket_type_supports_attributes(socket_type)) {
if (!(item.flag & GEO_NODE_BAKE_ITEM_IS_ATTRIBUTE)) {
output_decl.inferred_structure_type({input_decl.index()});
}
}
input_decl.structure_type(StructureType::Dynamic);
output_decl.structure_type(StructureType::Dynamic);
if (socket_type == SOCK_BUNDLE) {
dynamic_cast<decl::BundleBuilder &>(output_decl)
.pass_through_input_index(input_decl.index());
}
}
b.add_input<decl::Extend>(""_ustr, "__extend__"_ustr)
.structure_type(StructureType::Dynamic)
.custom_draw(socket_items::ui::draw_extend_socket_fn<BakeItemsAccessor>());
b.add_output<decl::Extend>(""_ustr, "__extend__"_ustr)
.structure_type(StructureType::Dynamic)
.align_with_previous();
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryBake *data = MEM_new<NodeGeometryBake>(__func__);
node->storage = data;
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<BakeItemsAccessor>(*node);
MEM_delete(reinterpret_cast<NodeGeometryBake *>(node->storage));
}
static void node_copy_storage(bNodeTree * /*tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeGeometryBake &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_new<NodeGeometryBake>(__func__, dna::shallow_copy(src_storage));
dst_node->storage = dst_storage;
socket_items::copy_array<BakeItemsAccessor>(*src_node, *dst_node);
}
static bool node_insert_link(bke::NodeInsertLinkParams &params)
{
return socket_items::try_add_item_via_any_extend_socket<BakeItemsAccessor>(
params.ntree, params.node, params.node, params.link);
}
static void draw_bake_items(const bContext *C, ui::Layout &layout, PointerRNA node_ptr)
{
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(node_ptr.owner_id);
bNode &node = *static_cast<bNode *>(node_ptr.data);
NodeGeometryBake &storage = node_storage(node);
if (ui::Layout *panel = layout.panel(C, "bake_items", false, IFACE_("Bake Items"))) {
socket_items::ui::draw_items_list_with_operators<BakeItemsAccessor>(C, panel, tree, node);
socket_items::ui::draw_active_item_props<BakeItemsAccessor>(
tree, node, [&](PointerRNA *item_ptr) {
const NodeGeometryBakeItem &active_item = storage.items[storage.active_index];
const eNodeSocketDatatype socket_type = active_item.socket_type;
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
if (socket_type_supports_attributes(socket_type)) {
panel->prop(item_ptr, "attribute_domain", UI_ITEM_NONE, std::nullopt, ICON_NONE);
panel->prop(item_ptr, "is_attribute", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
});
}
}
static void node_operators()
{
socket_items::ops::make_common_operators<BakeItemsAccessor>();
}
/**
* This is used when the bake node should just pass-through the data and the caller of geometry
* nodes should not have to care about this.
*/
struct DummyDataBlockMap : public bake::BakeDataBlockMap {
private:
Mutex mutex_;
Map<bake::BakeDataBlockID, ID *> map_;
public:
ID *lookup_or_remember_missing(const bake::BakeDataBlockID &key) override
{
std::lock_guard lock{mutex_};
return map_.lookup_default(key, nullptr);
}
void try_add(ID &id) override
{
std::lock_guard lock{mutex_};
map_.add(bake::BakeDataBlockID(id), &id);
}
};
class LazyFunctionForBakeNode final : public LazyFunction {
const bNode &node_;
Span<NodeGeometryBakeItem> bake_items_;
public:
LazyFunctionForBakeNode(const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
: node_(node)
{
debug_name_ = "Bake";
const NodeGeometryBake &storage = node_storage(node);
bake_items_ = {storage.items, storage.items_num};
MutableSpan<int> lf_index_by_bsocket = lf_graph_info.mapping.lf_index_by_bsocket;
for (const int i : bake_items_.index_range()) {
const NodeGeometryBakeItem &item = bake_items_[i];
const bNodeSocket &input_bsocket = node.input_socket(i);
const bNodeSocket &output_bsocket = node.output_socket(i);
lf_index_by_bsocket[input_bsocket.index_in_tree()] = inputs_.append_and_get_index_as(
item.name, CPPType::get<SocketValueVariant>(), lf::ValueUsage::Maybe);
lf_index_by_bsocket[output_bsocket.index_in_tree()] = outputs_.append_and_get_index_as(
item.name, CPPType::get<SocketValueVariant>());
}
}
void execute_impl(lf::Params &params, const lf::Context &context) const final
{
GeoNodesUserData &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
GeoNodesLocalUserData &local_user_data = *static_cast<GeoNodesLocalUserData *>(
context.local_user_data);
if (!user_data.call_data->self_object()) {
/* The self object is currently required for generating anonymous attribute names. */
this->set_default_outputs(params);
return;
}
if (!user_data.call_data->bake_params) {
this->set_default_outputs(params);
return;
}
std::optional<FoundNestedNodeID> found_id = find_nested_node_id(user_data, node_.identifier);
if (!found_id) {
this->set_default_outputs(params);
return;
}
if (found_id->is_in_loop || found_id->is_in_closure) {
DummyDataBlockMap data_block_map;
this->pass_through(params, user_data, &data_block_map);
return;
}
BakeNodeBehavior *behavior = user_data.call_data->bake_params->get(found_id->id);
if (!behavior) {
this->set_default_outputs(params);
return;
}
if (auto *info = std::get_if<sim_output::ReadSingle>(&behavior->behavior)) {
this->output_cached_state(params, user_data, behavior->data_block_map, info->values);
}
else if (auto *info = std::get_if<sim_output::ReadInterpolated>(&behavior->behavior)) {
this->output_mixed_cached_state(params,
behavior->data_block_map,
*user_data.compute_context,
info->prev_values,
info->next_values,
info->mix_factor);
}
else if (std::get_if<sim_output::PassThrough>(&behavior->behavior)) {
this->pass_through(params, user_data, behavior->data_block_map);
}
else if (auto *info = std::get_if<sim_output::StoreNewState>(&behavior->behavior)) {
this->store(params, user_data, behavior->data_block_map, *info);
}
else if (auto *info = std::get_if<sim_output::ReadError>(&behavior->behavior)) {
if (eval_log::NodeTreeLogger *tree_logger = local_user_data.try_get_tree_logger(user_data)) {
tree_logger->node_warnings.append(
*tree_logger->allocator, {node_.identifier, {NodeWarningType::Error, info->message}});
}
this->set_default_outputs(params);
}
else {
BLI_assert_unreachable();
}
}
void set_default_outputs(lf::Params &params) const
{
set_default_remaining_node_outputs(params, node_);
}
void pass_through(lf::Params &params,
GeoNodesUserData &user_data,
bke::bake::BakeDataBlockMap *data_block_map) const
{
std::optional<bake::BakeValues> bake_values = this->get_bake_values_from_inputs(
params, data_block_map, false);
if (!bake_values) {
/* Wait for inputs to be computed. */
return;
}
Vector<SocketValueVariant> output_values = this->bake_to_output_values(
std::move(*bake_values), data_block_map, *user_data.compute_context);
for (const int i : bake_items_.index_range()) {
params.set_output(i, std::move(output_values[i]));
}
}
void store(lf::Params &params,
GeoNodesUserData &user_data,
bke::bake::BakeDataBlockMap *data_block_map,
const sim_output::StoreNewState &info) const
{
std::optional<bake::BakeValues> bake_values = this->get_bake_values_from_inputs(
params, data_block_map);
if (!bake_values) {
/* Wait for inputs to be computed. */
return;
}
this->output_cached_state(params, user_data, data_block_map, *bake_values);
info.store_fn(std::move(*bake_values));
}
void output_cached_state(lf::Params &params,
GeoNodesUserData &user_data,
bke::bake::BakeDataBlockMap *data_block_map,
const bake::BakeValues &bake_values) const
{
Vector<SocketValueVariant> values = this->bake_to_output_values(
bake_values, data_block_map, *user_data.compute_context);
for (const int i : bake_items_.index_range()) {
params.set_output(i, std::move(values[i]));
}
}
void output_mixed_cached_state(lf::Params &params,
bke::bake::BakeDataBlockMap *data_block_map,
const ComputeContext &compute_context,
const bake::BakeValues &prev_bake_values,
const bake::BakeValues &next_bake_values,
const float mix_factor) const
{
Vector<SocketValueVariant> output_values = this->bake_to_output_values(
prev_bake_values, data_block_map, compute_context);
Vector<SocketValueVariant> next_values = this->bake_to_output_values(
next_bake_values, data_block_map, compute_context);
for (const int i : bake_items_.index_range()) {
geometry::mix_socket_values(output_values[i], next_values[i], mix_factor);
}
for (const int i : bake_items_.index_range()) {
params.set_output(i, std::move(output_values[i]));
}
}
std::optional<bake::BakeValues> get_bake_values_from_inputs(
lf::Params &params,
bke::bake::BakeDataBlockMap *data_block_map,
const bool is_for_cache = true) const
{
Array<bke::SocketValueVariant *> input_value_pointers(bake_items_.size());
for (const int i : bake_items_.index_range()) {
input_value_pointers[i] = params.try_get_input_data_ptr_or_request<bke::SocketValueVariant>(
i);
}
if (input_value_pointers.as_span().contains(nullptr)) {
/* Wait for inputs to be computed. */
return std::nullopt;
}
Vector<bake::BakeValues::InputValue> bake_input_values(bake_items_.size());
for (const int i : bake_items_.index_range()) {
const NodeGeometryBakeItem &item = bake_items_[i];
bake::BakeValues::InputValue &bake_input_value = bake_input_values[i];
bake_input_value.id = item.identifier;
bake_input_value.name = item.name;
bake_input_value.field_domain = AttrDomain(item.attribute_domain);
bake_input_value.value = std::move(*input_value_pointers[i]);
}
return bake::BakeValues::from_runtime_values(
std::move(bake_input_values), data_block_map, is_for_cache);
}
Vector<SocketValueVariant> bake_to_output_values(const bake::BakeValues &bake_values,
bke::bake::BakeDataBlockMap *data_block_map,
const ComputeContext &compute_context) const
{
Vector<bake::BakeValues::OutputKey> keys;
for (const NodeGeometryBakeItem &item : bake_items_) {
keys.append({item.identifier, item.socket_type});
}
return bake_values.to_runtime_values(keys, compute_context, data_block_map);
}
ImplicitSharingPtr<AttributeFieldInput> make_attribute_field(
const Object &self_object,
const ComputeContext &compute_context,
const NodeGeometryBakeItem &item,
const CPPType &type) const
{
std::string attribute_name = bke::hash_to_anonymous_attribute_name(
compute_context.hash(), self_object.id.name, node_.identifier, item.identifier);
std::string socket_inspection_name = make_anonymous_attribute_socket_inspection_string(
node_.label_or_name(), item.name);
return ImplicitSharingPtr<AttributeFieldInput>(MEM_new<AttributeFieldInput>(
__func__, std::move(attribute_name), type, std::move(socket_inspection_name)));
}
};
static void node_extra_info(NodeExtraInfoParams &params)
{
BakeDrawContext ctx;
if (!get_bake_draw_context(&params.C, params.node, ctx)) {
return;
}
if (!ctx.is_bakeable_in_current_context) {
NodeExtraInfoRow row;
row.text = TIP_("Cannot bake in zone");
row.icon = ICON_ERROR;
params.rows.append(std::move(row));
}
if (ctx.is_baked) {
NodeExtraInfoRow row;
row.text = get_baked_string(ctx);
params.rows.append(std::move(row));
}
}
static void node_layout(ui::Layout &layout, bContext *C, PointerRNA *ptr)
{
BakeDrawContext ctx;
const bNode &node = *static_cast<const bNode *>(ptr->data);
if (!get_bake_draw_context(C, node, ctx)) {
return;
}
layout.active_set(ctx.is_bakeable_in_current_context);
layout.enabled_set(ID_IS_EDITABLE(ctx.object));
ui::Layout &col = layout.column(false);
{
ui::Layout &row = col.row(true);
row.enabled_set(!ctx.is_baked);
row.prop(&ctx.bake_rna, "bake_mode", ui::ITEM_R_EXPAND, IFACE_("Mode"), ICON_NONE);
}
draw_bake_button_row(ctx, col);
}
static void node_layout_ex(ui::Layout &layout, bContext *C, PointerRNA *ptr)
{
draw_bake_items(C, layout, *ptr);
BakeDrawContext ctx;
const bNode &node = *static_cast<const bNode *>(ptr->data);
if (!get_bake_draw_context(C, node, ctx)) {
return;
}
layout.active_set(ctx.is_bakeable_in_current_context);
layout.enabled_set(ID_IS_EDITABLE(ctx.object));
{
ui::Layout &col = layout.column(false);
{
ui::Layout &row = col.row(true);
row.enabled_set(!ctx.is_baked);
row.prop(&ctx.bake_rna, "bake_mode", ui::ITEM_R_EXPAND, IFACE_("Mode"), ICON_NONE);
}
draw_bake_button_row(ctx, col, true);
if (const std::optional<std::string> bake_state_str = get_bake_state_string(ctx)) {
ui::Layout &row = col.row(true);
row.label(*bake_state_str, ICON_NONE);
}
}
draw_common_bake_settings(C, ctx, layout);
draw_data_blocks(C, layout, ctx.bake_rna);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const eNodeSocketDatatype type = params.other_socket().type;
if (!BakeItemsAccessor::supports_socket_type(type, params.node_tree().type)) {
return;
}
params.add_item(
IFACE_("Value"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeBake"_ustr);
socket_items::add_item_with_socket_type_and_name<BakeItemsAccessor>(
params.node_tree, node, type, params.socket.name);
params.update_and_connect_available_socket(node, UString(params.socket.name));
},
-1);
}
static const bNodeSocket *node_internally_linked_input(const bNodeTree & /*tree*/,
const bNode &node,
const bNodeSocket &output_socket)
{
/* Internal links should always map corresponding input and output sockets. */
return node.input_by_identifier(output_socket.identifier_ustr());
}
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
{
socket_items::blend_write<BakeItemsAccessor>(&writer, node);
}
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
{
socket_items::blend_read_data<BakeItemsAccessor>(&reader, node);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeBake"_ustr, GEO_NODE_BAKE);
ntype.ui_name = "Bake";
ntype.ui_description = "Cache the incoming data so that it can be used without recomputation";
ntype.enum_name_legacy = "BAKE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.initfunc = node_init;
ntype.insert_link = node_insert_link;
ntype.draw_buttons_ex = node_layout_ex;
ntype.get_extra_info = node_extra_info;
ntype.register_operators = node_operators;
ntype.gather_link_search_ops = node_gather_link_searches;
ntype.internally_linked_input = node_internally_linked_input;
ntype.blend_write_storage_content = node_blend_write;
ntype.blend_data_read_storage_content = node_blend_read;
bke::node_type_storage(ntype, "NodeGeometryBake", node_free_storage, node_copy_storage);
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace nodes::node_geo_bake_cc
namespace nodes {
bool get_bake_draw_context(const bContext *C, const bNode &node, BakeDrawContext &r_ctx)
{
BLI_assert(ELEM(node.type_legacy, GEO_NODE_BAKE, GEO_NODE_SIMULATION_OUTPUT));
r_ctx.node = &node;
r_ctx.snode = CTX_wm_space_node(C);
if (!r_ctx.snode) {
return false;
}
std::optional<ed::space_node::ObjectAndModifier> object_and_modifier =
ed::space_node::get_modifier_for_node_editor(*r_ctx.snode);
if (!object_and_modifier) {
return false;
}
r_ctx.object = object_and_modifier->object;
r_ctx.nmd = object_and_modifier->nmd;
const std::optional<FoundNestedNodeID> bake_id = ed::space_node::find_nested_node_id_in_root(
*r_ctx.snode, *r_ctx.node);
if (!bake_id) {
return false;
}
r_ctx.is_bakeable_in_current_context = !bake_id->is_in_loop && !bake_id->is_in_closure;
r_ctx.bake = nullptr;
for (const NodesModifierBake &iter_bake : Span(r_ctx.nmd->bakes, r_ctx.nmd->bakes_num)) {
if (iter_bake.id == bake_id->id) {
r_ctx.bake = &iter_bake;
break;
}
}
if (!r_ctx.bake) {
return false;
}
PointerRNA modifier_ptr = RNA_pointer_create_id_subdata(
const_cast<ID &>(r_ctx.object->id),
RNA_NodesModifier,
const_cast<NodesModifierData *>(r_ctx.nmd));
r_ctx.bake_rna = RNA_pointer_create_with_parent(
modifier_ptr, RNA_NodesModifierBake, const_cast<NodesModifierBake *>(r_ctx.bake));
if (r_ctx.nmd->runtime->cache) {
const bke::bake::ModifierCache &cache = *r_ctx.nmd->runtime->cache;
std::lock_guard lock{cache.mutex};
if (const std::unique_ptr<bke::bake::BakeNodeCache> *node_cache_ptr =
cache.bake_cache_by_id.lookup_ptr(bake_id->id))
{
const bke::bake::BakeNodeCache &node_cache = **node_cache_ptr;
if (!node_cache.bake.frames.is_empty()) {
const int first_frame = node_cache.bake.frames.first()->frame.frame();
const int last_frame = node_cache.bake.frames.last()->frame.frame();
r_ctx.baked_range = IndexRange(first_frame, last_frame - first_frame + 1);
}
}
else if (const std::unique_ptr<bke::bake::SimulationNodeCache> *node_cache_ptr =
cache.simulation_cache_by_id.lookup_ptr(bake_id->id))
{
const bke::bake::SimulationNodeCache &node_cache = **node_cache_ptr;
if (!node_cache.bake.frames.is_empty() &&
node_cache.cache_status == bke::bake::CacheStatus::Baked)
{
const int first_frame = node_cache.bake.frames.first()->frame.frame();
const int last_frame = node_cache.bake.frames.last()->frame.frame();
r_ctx.baked_range = IndexRange(first_frame, last_frame - first_frame + 1);
}
}
}
const Scene *scene = CTX_data_scene(C);
r_ctx.frame_range = bke::bake::get_node_bake_frame_range(
*scene, *r_ctx.object, *r_ctx.nmd, r_ctx.bake->id);
r_ctx.bake_still = node.type_legacy == GEO_NODE_BAKE &&
r_ctx.bake->bake_mode == NODES_MODIFIER_BAKE_MODE_STILL;
r_ctx.is_baked = r_ctx.baked_range.has_value();
r_ctx.bake_target = bke::bake::get_node_bake_target(*r_ctx.object, *r_ctx.nmd, r_ctx.bake->id);
return true;
}
std::string get_baked_string(const BakeDrawContext &ctx)
{
if (ctx.bake_still && ctx.baked_range->size() == 1) {
return fmt::format(fmt::runtime(RPT_("Baked Frame {}")), ctx.baked_range->first());
}
return fmt::format(
fmt::runtime(RPT_("Baked {} - {}")), ctx.baked_range->first(), ctx.baked_range->last());
}
std::optional<std::string> get_bake_state_string(const BakeDrawContext &ctx)
{
if (G.is_rendering) {
/* Avoid accessing data that is generated while baking. */
return std::nullopt;
}
if (ctx.is_baked) {
const std::string baked_str = get_baked_string(ctx);
char size_str[BLI_STR_FORMAT_INT64_BYTE_UNIT_SIZE];
BLI_str_format_byte_unit(size_str, ctx.bake->bake_size, true);
if (ctx.bake->packed) {
return fmt::format(fmt::runtime(RPT_("{} ({} packed)")), baked_str, size_str);
}
return fmt::format(fmt::runtime(RPT_("{} ({} on disk)")), baked_str, size_str);
}
if (ctx.frame_range.has_value()) {
if (!ctx.bake_still) {
return fmt::format(
fmt::runtime(RPT_("Frames {} - {}")), ctx.frame_range->first(), ctx.frame_range->last());
}
}
return std::nullopt;
}
void draw_bake_button_row(const BakeDrawContext &ctx, ui::Layout &layout, const bool is_in_sidebar)
{
ui::Layout &col = layout.column(true);
ui::Layout &row = col.row(true);
{
const char *bake_label = IFACE_("Bake");
if (is_in_sidebar) {
bake_label = ctx.bake_target == NODES_MODIFIER_BAKE_TARGET_DISK ? IFACE_("Bake to Disk") :
IFACE_("Bake Packed");
}
PointerRNA ptr = row.op("OBJECT_OT_geometry_node_bake_single",
bake_label,
ICON_NONE,
wm::OpCallContext::InvokeDefault,
UI_ITEM_NONE);
WM_operator_properties_id_lookup_set_from_id(&ptr, &ctx.object->id);
RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name);
RNA_int_set(&ptr, "bake_id", ctx.bake->id);
}
{
ui::Layout &subrow = row.row(true);
subrow.active_set(ctx.is_baked);
if (is_in_sidebar) {
if (ctx.is_baked && !G.is_rendering) {
if (ctx.bake->packed) {
PointerRNA ptr = subrow.op("OBJECT_OT_geometry_node_bake_unpack_single",
"",
ICON_PACKAGE,
wm::OpCallContext::InvokeDefault,
UI_ITEM_NONE);
WM_operator_properties_id_lookup_set_from_id(&ptr, &ctx.object->id);
RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name);
RNA_int_set(&ptr, "bake_id", ctx.bake->id);
}
else {
PointerRNA ptr = subrow.op("OBJECT_OT_geometry_node_bake_pack_single",
"",
ICON_UGLYPACKAGE,
wm::OpCallContext::InvokeDefault,
UI_ITEM_NONE);
WM_operator_properties_id_lookup_set_from_id(&ptr, &ctx.object->id);
RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name);
RNA_int_set(&ptr, "bake_id", ctx.bake->id);
}
}
else {
/* If the data is not yet baked, still show the icon based on the derived bake target. */
const int icon = ctx.bake_target == NODES_MODIFIER_BAKE_TARGET_DISK ? ICON_UGLYPACKAGE :
ICON_PACKAGE;
PointerRNA ptr = subrow.op("OBJECT_OT_geometry_node_bake_pack_single",
"",
icon,
wm::OpCallContext::InvokeDefault,
UI_ITEM_NONE);
}
}
{
PointerRNA ptr = subrow.op("OBJECT_OT_geometry_node_bake_delete_single",
"",
ICON_TRASH,
wm::OpCallContext::InvokeDefault,
UI_ITEM_NONE);
WM_operator_properties_id_lookup_set_from_id(&ptr, &ctx.object->id);
RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name);
RNA_int_set(&ptr, "bake_id", ctx.bake->id);
}
}
}
void draw_common_bake_settings(bContext *C, BakeDrawContext &ctx, ui::Layout &layout)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
ui::Layout &settings_col = layout.column(false);
settings_col.active_set(!ctx.is_baked);
{
ui::Layout &col = settings_col.column(true);
col.prop(&ctx.bake_rna, "bake_target", UI_ITEM_NONE, std::nullopt, ICON_NONE);
ui::Layout &subcol = col.column(true);
subcol.active_set(ctx.bake_target == NODES_MODIFIER_BAKE_TARGET_DISK);
subcol.prop(&ctx.bake_rna, "use_custom_path", UI_ITEM_NONE, IFACE_("Custom Path"), ICON_NONE);
ui::Layout &subsubcol = subcol.column(true);
const bool use_custom_path = ctx.bake->flag & NODES_MODIFIER_BAKE_CUSTOM_PATH;
subsubcol.active_set(use_custom_path);
Main *bmain = CTX_data_main(C);
auto bake_path = bke::bake::get_node_bake_path(*bmain, *ctx.object, *ctx.nmd, ctx.bake->id);
char placeholder_path[FILE_MAX] = "";
if (StringRef(ctx.bake->directory).is_empty() &&
!(ctx.bake->flag & NODES_MODIFIER_BAKE_CUSTOM_PATH) && bake_path.has_value() &&
bake_path->bake_dir.has_value())
{
STRNCPY(placeholder_path, bake_path->bake_dir->c_str());
if (BLI_path_is_rel(ctx.nmd->bake_directory)) {
BLI_path_rel(placeholder_path, BKE_main_blendfile_path(bmain));
}
}
subsubcol.prop(&ctx.bake_rna,
RNA_struct_find_property(&ctx.bake_rna, "directory"),
-1,
0,
UI_ITEM_NONE,
IFACE_("Path"),
ICON_NONE,
placeholder_path);
}
{
ui::Layout &col = settings_col.column(true);
col.prop(&ctx.bake_rna,
"use_custom_simulation_frame_range",
UI_ITEM_NONE,
IFACE_("Custom Range"),
ICON_NONE);
ui::Layout &subcol = col.column(true);
subcol.active_set(ctx.bake->flag & NODES_MODIFIER_BAKE_CUSTOM_SIMULATION_FRAME_RANGE);
subcol.prop(&ctx.bake_rna, "frame_start", UI_ITEM_NONE, IFACE_("Start"), ICON_NONE);
subcol.prop(&ctx.bake_rna, "frame_end", UI_ITEM_NONE, IFACE_("End"), ICON_NONE);
}
}
static void draw_bake_data_block_list_item(uiList * /*ui_list*/,
const bContext * /*C*/,
ui::Layout &layout,
PointerRNA * /*idataptr*/,
PointerRNA *itemptr,
int /*icon*/,
PointerRNA * /*active_dataptr*/,
const char * /*active_propname*/,
int /*index*/,
int /*flt_flag*/)
{
auto &data_block = *static_cast<NodesModifierDataBlock *>(itemptr->data);
ui::Layout &row = layout.row(true);
std::string name;
if (StringRef(data_block.lib_name).is_empty()) {
name = data_block.id_name;
}
else {
name = fmt::format("{} [{}]", data_block.id_name, data_block.lib_name);
}
row.prop(itemptr, "id", UI_ITEM_NONE, name, ICON_NONE);
}
void draw_data_blocks(const bContext *C, ui::Layout &layout, PointerRNA &bake_rna)
{
static const uiListType *data_block_list = []() {
uiListType *list = MEM_new_zeroed<uiListType>(__func__);
STRNCPY_UTF8(list->idname, "DATA_UL_nodes_modifier_data_blocks");
list->draw_item = draw_bake_data_block_list_item;
WM_uilisttype_add(list);
return list;
}();
PointerRNA data_blocks_ptr = RNA_pointer_create_discrete(
bake_rna.owner_id, RNA_NodesModifierBakeDataBlocks, bake_rna.data);
if (ui::Layout *panel = layout.panel(
C, "data_block_references", true, IFACE_("Data-Block References")))
{
ui::template_uilist(panel,
C,
data_block_list->idname,
"",
&bake_rna,
"data_blocks",
&data_blocks_ptr,
"active_index",
nullptr,
3,
5,
UILST_LAYOUT_DEFAULT,
ui::TEMPLATE_LIST_FLAG_NONE);
}
}
std::unique_ptr<LazyFunction> get_bake_lazy_function(
const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
{
namespace file_ns = nodes::node_geo_bake_cc;
BLI_assert(node.type_legacy == GEO_NODE_BAKE);
return std::make_unique<file_ns::LazyFunctionForBakeNode>(node, lf_graph_info);
}
StructRNA **BakeItemsAccessor::item_srna = &RNA_NodeGeometryBakeItem;
void BakeItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
writer->write_string(item.name);
}
void BakeItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
}; // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,503 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_generic_array.hh"
#include "BLI_index_mask.hh"
#include "BLI_index_range.hh"
#include "BLI_span.hh"
#include "BLI_task.hh"
#include "BLI_virtual_array.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_fields.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_mapping.hh"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "RNA_enum_types.hh"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_blur_attribute_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
const bNode *node = b.node_or_null();
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node->custom1);
b.add_input(data_type, "Value"_ustr)
.structure_type(StructureType::Field)
.hide_value()
.is_default_link_socket();
b.add_output(data_type, "Value"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.align_with_previous();
}
b.add_input<decl::Int>("Iterations"_ustr)
.default_value(1)
.min(0)
.description("How many times to blur the values for all elements");
b.add_input<decl::Float>("Weight"_ustr)
.default_value(1.0f)
.subtype(PROP_FACTOR)
.min(0.0f)
.max(1.0f)
.structure_type(StructureType::Field)
.description("Relative mix weight of neighboring elements");
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = CD_PROP_FLOAT;
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const bke::bNodeType &node_type = params.node_type();
const NodeDeclaration &declaration = *node_type.static_declaration;
/* Weight and Iterations inputs don't change based on the data type. */
search_link_ops_for_declarations(params, declaration.inputs);
const std::optional<eCustomDataType> new_node_type = bke::socket_type_to_custom_data_type(
params.other_socket().type);
if (!new_node_type.has_value()) {
return;
}
eCustomDataType fixed_data_type = *new_node_type;
if (fixed_data_type == CD_PROP_STRING) {
return;
}
if (fixed_data_type == CD_PROP_QUATERNION) {
fixed_data_type = CD_PROP_FLOAT3;
}
if (fixed_data_type == CD_PROP_FLOAT4X4) {
/* Don't implement matrix blurring for now. */
return;
}
if (fixed_data_type == CD_PROP_BOOL) {
/* This node does not support boolean sockets, use integer instead. */
fixed_data_type = CD_PROP_INT32;
}
params.add_item(IFACE_("Value"), [node_type, fixed_data_type](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
node.custom1 = fixed_data_type;
params.update_and_connect_available_socket(node, "Value"_ustr);
});
}
static void build_vert_to_vert_by_edge_map(const Span<int2> edges,
const int verts_num,
Array<int> &r_offsets,
Array<int> &r_indices)
{
bke::mesh::build_vert_to_edge_map(edges, verts_num, r_offsets, r_indices);
const OffsetIndices<int> offsets(r_offsets);
threading::parallel_for(IndexRange(verts_num), 2048, [&](const IndexRange range) {
for (const int vert : range) {
MutableSpan<int> neighbors = r_indices.as_mutable_span().slice(offsets[vert]);
for (const int i : neighbors.index_range()) {
neighbors[i] = bke::mesh::edge_other_vert(edges[neighbors[i]], vert);
}
}
});
}
static void build_edge_to_edge_by_vert_map(const Span<int2> edges,
const int verts_num,
Array<int> &r_offsets,
Array<int> &r_indices)
{
Array<int> vert_to_edge_offset_data;
Array<int> vert_to_edge_indices;
const GroupedSpan<int> vert_to_edge = bke::mesh::build_vert_to_edge_map(
edges, verts_num, vert_to_edge_offset_data, vert_to_edge_indices);
const OffsetIndices<int> vert_to_edge_offsets(vert_to_edge_offset_data);
r_offsets = Array<int>(edges.size() + 1, 0);
threading::parallel_for(edges.index_range(), 1024, [&](const IndexRange range) {
for (const int edge_i : range) {
const int2 edge = edges[edge_i];
r_offsets[edge_i] = vert_to_edge_offsets[edge[0]].size() - 1 +
vert_to_edge_offsets[edge[1]].size() - 1;
}
});
const OffsetIndices offsets = offset_indices::accumulate_counts_to_offsets(r_offsets);
r_indices.reinitialize(offsets.total_size());
threading::parallel_for(edges.index_range(), 1024, [&](const IndexRange range) {
for (const int edge_i : range) {
const int2 edge = edges[edge_i];
MutableSpan<int> neighbors = r_indices.as_mutable_span().slice(offsets[edge_i]);
int count = 0;
for (const Span<int> neighbor_edges : {vert_to_edge[edge[0]], vert_to_edge[edge[1]]}) {
for (const int neighbor_edge : neighbor_edges) {
if (neighbor_edge != edge_i) {
neighbors[count] = neighbor_edge;
count++;
}
}
}
}
});
}
static void build_face_to_face_by_edge_map(const OffsetIndices<int> faces,
const Span<int> corner_edges,
const int edges_num,
Array<int> &r_offsets,
Array<int> &r_indices)
{
Array<int> edge_to_face_offset_data;
Array<int> edge_to_face_indices;
const GroupedSpan<int> edge_to_face_map = bke::mesh::build_edge_to_face_map(
faces, corner_edges, edges_num, edge_to_face_offset_data, edge_to_face_indices);
const OffsetIndices<int> edge_to_face_offsets(edge_to_face_offset_data);
r_offsets = Array<int>(faces.size() + 1, 0);
threading::parallel_for(faces.index_range(), 4096, [&](const IndexRange range) {
for (const int face_i : range) {
for (const int edge : corner_edges.slice(faces[face_i])) {
/* Subtract face itself from the number of faces connected to the edge. */
r_offsets[face_i] += edge_to_face_offsets[edge].size() - 1;
}
}
});
const OffsetIndices<int> offsets = offset_indices::accumulate_counts_to_offsets(r_offsets);
r_indices.reinitialize(offsets.total_size());
threading::parallel_for(faces.index_range(), 1024, [&](IndexRange range) {
for (const int face_i : range) {
MutableSpan<int> neighbors = r_indices.as_mutable_span().slice(offsets[face_i]);
if (neighbors.is_empty()) {
continue;
}
int count = 0;
for (const int edge : corner_edges.slice(faces[face_i])) {
for (const int neighbor : edge_to_face_map[edge]) {
if (neighbor != face_i) {
neighbors[count] = neighbor;
count++;
}
}
}
}
});
}
static GroupedSpan<int> create_mesh_map(const Mesh &mesh,
const AttrDomain domain,
Array<int> &r_offsets,
Array<int> &r_indices)
{
switch (domain) {
case AttrDomain::Point:
build_vert_to_vert_by_edge_map(mesh.edges(), mesh.verts_num, r_offsets, r_indices);
break;
case AttrDomain::Edge:
build_edge_to_edge_by_vert_map(mesh.edges(), mesh.verts_num, r_offsets, r_indices);
break;
case AttrDomain::Face:
build_face_to_face_by_edge_map(
mesh.faces(), mesh.corner_edges(), mesh.edges_num, r_offsets, r_indices);
break;
default:
BLI_assert_unreachable();
break;
}
return {OffsetIndices<int>(r_offsets), r_indices};
}
template<typename T>
static Span<T> blur_on_mesh_exec(const Span<float> neighbor_weights,
const GroupedSpan<int> neighbors_map,
const int iterations,
const MutableSpan<T> buffer_a,
const MutableSpan<T> buffer_b)
{
/* Source is set to buffer_b even though it is actually in buffer_a because the loop below starts
* with swapping both. */
MutableSpan<T> src = buffer_b;
MutableSpan<T> dst = buffer_a;
for ([[maybe_unused]] const int64_t iteration : IndexRange(iterations)) {
std::swap(src, dst);
bke::attribute_math::DefaultMixer<T> mixer{dst, IndexMask(0)};
threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
for (const int64_t index : range) {
const Span<int> neighbors = neighbors_map[index];
const float neighbor_weight = neighbor_weights[index];
mixer.set(index, src[index], 1.0f);
for (const int neighbor : neighbors) {
mixer.mix_in(index, src[neighbor], neighbor_weight);
}
}
mixer.finalize(range);
});
}
return dst;
}
template<typename Func> static void to_static_type_for_blur(const CPPType &type, const Func &func)
{
type.to_static_type<int, float, float3, ColorGeometry4f>([&]<typename T>() { func(T()); });
}
static GSpan blur_on_mesh(const Mesh &mesh,
const AttrDomain domain,
const int iterations,
const Span<float> neighbor_weights,
const GMutableSpan buffer_a,
const GMutableSpan buffer_b)
{
Array<int> neighbor_offsets;
Array<int> neighbor_indices;
const GroupedSpan<int> neighbors_map = create_mesh_map(
mesh, domain, neighbor_offsets, neighbor_indices);
GSpan result_buffer;
to_static_type_for_blur(buffer_a.type(), [&](auto dummy) {
using T = decltype(dummy);
result_buffer = blur_on_mesh_exec<T>(
neighbor_weights, neighbors_map, iterations, buffer_a.typed<T>(), buffer_b.typed<T>());
});
return result_buffer;
}
template<typename T>
static Span<T> blur_on_curve_exec(const bke::CurvesGeometry &curves,
const Span<float> neighbor_weights,
const int iterations,
const MutableSpan<T> buffer_a,
const MutableSpan<T> buffer_b)
{
MutableSpan<T> src = buffer_b;
MutableSpan<T> dst = buffer_a;
const OffsetIndices points_by_curve = curves.points_by_curve();
const VArray<bool> cyclic = curves.cyclic();
for ([[maybe_unused]] const int iteration : IndexRange(iterations)) {
std::swap(src, dst);
bke::attribute_math::DefaultMixer<T> mixer{dst, IndexMask(0)};
threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) {
for (const int curve_i : range) {
const IndexRange points = points_by_curve[curve_i];
if (points.size() == 1) {
/* No mixing possible. */
const int point_i = points[0];
mixer.set(point_i, src[point_i], 1.0f);
continue;
}
/* Inner points. */
for (const int point_i : points.drop_front(1).drop_back(1)) {
const float neighbor_weight = neighbor_weights[point_i];
mixer.set(point_i, src[point_i], 1.0f);
mixer.mix_in(point_i, src[point_i - 1], neighbor_weight);
mixer.mix_in(point_i, src[point_i + 1], neighbor_weight);
}
const int first_i = points[0];
const float first_neighbor_weight = neighbor_weights[first_i];
const int last_i = points.last();
const float last_neighbor_weight = neighbor_weights[last_i];
/* First point. */
mixer.set(first_i, src[first_i], 1.0f);
mixer.mix_in(first_i, src[first_i + 1], first_neighbor_weight);
/* Last point. */
mixer.set(last_i, src[last_i], 1.0f);
mixer.mix_in(last_i, src[last_i - 1], last_neighbor_weight);
if (cyclic[curve_i]) {
/* First point. */
mixer.mix_in(first_i, src[last_i], first_neighbor_weight);
/* Last point. */
mixer.mix_in(last_i, src[first_i], last_neighbor_weight);
}
}
mixer.finalize(points_by_curve[range]);
});
}
return dst;
}
static GSpan blur_on_curves(const bke::CurvesGeometry &curves,
const int iterations,
const Span<float> neighbor_weights,
const GMutableSpan buffer_a,
const GMutableSpan buffer_b)
{
GSpan result_buffer;
to_static_type_for_blur(buffer_a.type(), [&](auto dummy) {
using T = decltype(dummy);
result_buffer = blur_on_curve_exec<T>(
curves, neighbor_weights, iterations, buffer_a.typed<T>(), buffer_b.typed<T>());
});
return result_buffer;
}
class BlurAttributeFieldInput final : public bke::GeometryFieldInput {
private:
const Field<float> weight_field_;
const GField value_field_;
const int iterations_;
public:
BlurAttributeFieldInput(Field<float> weight_field, GField value_field, const int iterations)
: bke::GeometryFieldInput(value_field.cpp_type(), "Blur Attribute"),
weight_field_(std::move(weight_field)),
value_field_(std::move(value_field)),
iterations_(iterations)
{
}
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
const IndexMask & /*mask*/) const final
{
const int64_t domain_size = context.attributes()->domain_size(context.domain());
GArray<> buffer_a(*type_, domain_size);
FieldEvaluator evaluator(context, domain_size);
evaluator.add_with_destination(value_field_, buffer_a.as_mutable_span());
evaluator.add(weight_field_);
evaluator.evaluate();
/* Blurring does not make sense with a less than 2 elements. */
if (domain_size <= 1) {
return GVArray::from_garray(std::move(buffer_a));
}
if (iterations_ <= 0) {
return GVArray::from_garray(std::move(buffer_a));
}
VArraySpan<float> neighbor_weights = evaluator.get_evaluated<float>(1);
GArray<> buffer_b(*type_, domain_size);
GSpan result_buffer = buffer_a.as_span();
switch (context.type()) {
case GeometryComponent::Type::Mesh:
if (ELEM(context.domain(), AttrDomain::Point, AttrDomain::Edge, AttrDomain::Face)) {
if (const Mesh *mesh = context.mesh()) {
result_buffer = blur_on_mesh(
*mesh, context.domain(), iterations_, neighbor_weights, buffer_a, buffer_b);
}
}
break;
case GeometryComponent::Type::Curve:
case GeometryComponent::Type::GreasePencil:
if (context.domain() == AttrDomain::Point) {
if (const bke::CurvesGeometry *curves = context.curves_or_strokes()) {
result_buffer = blur_on_curves(
*curves, iterations_, neighbor_weights, buffer_a, buffer_b);
}
}
break;
default:
break;
}
BLI_assert(ELEM(result_buffer.data(), buffer_a.data(), buffer_b.data()));
if (result_buffer.data() == buffer_a.data()) {
return GVArray::from_garray(std::move(buffer_a));
}
return GVArray::from_garray(std::move(buffer_b));
}
void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const override
{
fn(weight_field_);
fn(value_field_);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(weight_field_));
hash.add(deep_hash_cache.ensure(value_field_));
hash.add(iterations_);
}
std::optional<AttrDomain> preferred_domain(const GeometryComponent &component) const override
{
const std::optional<AttrDomain> domain = bke::try_detect_field_domain(component, value_field_);
if (domain.has_value() && *domain == AttrDomain::Corner) {
return AttrDomain::Point;
}
return domain;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
const int iterations = params.extract_input<int>("Iterations"_ustr);
Field<float> weight_field = params.extract_input<Field<float>>("Weight"_ustr);
GField value_field = params.extract_input<GField>("Value"_ustr);
params.set_output<GField>("Value"_ustr,
GField::from_input<BlurAttributeFieldInput>(
std::move(weight_field), std::move(value_field), iterations));
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(
srna,
"data_type",
"Data Type",
"",
rna_enum_attribute_type_items,
NOD_inline_enum_accessors(custom1),
CD_PROP_FLOAT,
[](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
*r_free = true;
return enum_items_filter(rna_enum_attribute_type_items, [](const EnumPropertyItem &item) {
return ELEM(item.value, CD_PROP_FLOAT, CD_PROP_FLOAT3, CD_PROP_COLOR, CD_PROP_INT32);
});
});
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeBlurAttribute"_ustr, GEO_NODE_BLUR_ATTRIBUTE);
ntype.ui_name = "Blur Attribute";
ntype.ui_description = "Mix attribute values of neighboring elements";
ntype.enum_name_legacy = "BLUR_ATTRIBUTE";
ntype.nclass = NODE_CLASS_ATTRIBUTE;
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.geometry_node_execute = node_geo_exec;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_blur_attribute_cc

View File

@@ -0,0 +1,216 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "BKE_action.hh"
#include "BKE_armature.hh"
#include "DEG_depsgraph_query.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_bone_info_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Object>("Armature"_ustr)
.optional_label()
.description("Armature object to retrieve the bone information from");
b.add_input<decl::String>("Bone Name"_ustr)
.optional_label()
.description("Name of the bone to retrieve");
b.add_output<decl::Matrix>("Pose"_ustr)
.description("Evaluated final transform of the bone in armature space");
b.add_output<decl::Matrix>("Local Pose"_ustr)
.description("Difference between the pose and rest pose relative to the parent bone");
b.add_output<decl::Matrix>("Transform Pose"_ustr)
.description("Matrix representing the bone's location, rotation, and scale properties");
b.add_output<decl::Matrix>("Rest Pose"_ustr)
.description("Original transform of the bone in armature space, defined in edit mode");
b.add_output<decl::Float>("Rest Length"_ustr).description("Original length of the bone");
b.add_output<decl::Bool>("Exists"_ustr).description("Whether the bone exists in the armature");
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "transform_space", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
static void node_gather_link_search_ops(GatherLinkSearchOpParams &params)
{
const eNodeSocketDatatype other_type = params.other_socket().type;
if (params.in_out() == SOCK_OUT) {
if (ELEM(other_type, SOCK_MATRIX, SOCK_ROTATION)) {
params.add_item(IFACE_("Pose"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeBoneInfo"_ustr);
params.update_and_connect_available_socket(node, "Pose"_ustr);
});
params.add_item(IFACE_("Local Pose"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeBoneInfo"_ustr);
params.update_and_connect_available_socket(node, "Local Pose"_ustr);
});
params.add_item(IFACE_("Transform Pose"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeBoneInfo"_ustr);
params.update_and_connect_available_socket(node, "Transform Pose"_ustr);
});
params.add_item(IFACE_("Rest Pose"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeBoneInfo"_ustr);
params.update_and_connect_available_socket(node, "Rest Pose"_ustr);
});
}
if (params.node_tree().typeinfo->validate_link(other_type, SOCK_FLOAT)) {
params.add_item(IFACE_("Rest Length"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeBoneInfo"_ustr);
params.update_and_connect_available_socket(node, "Rest Length"_ustr);
});
}
}
else {
if (other_type == SOCK_STRING) {
params.add_item(IFACE_("Bone Name"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeBoneInfo"_ustr);
params.update_and_connect_available_socket(node, "Bone Name"_ustr);
});
}
if (other_type == SOCK_OBJECT) {
params.add_item(IFACE_("Armature"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeBoneInfo"_ustr);
params.update_and_connect_available_socket(node, "Armature"_ustr);
});
}
}
}
static void node_node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = GEO_NODE_TRANSFORM_SPACE_ORIGINAL;
}
static void node_geo_exec(GeoNodeExecParams params)
{
Object *object = params.extract_input<Object *>("Armature"_ustr);
if (!object) {
params.set_default_remaining_outputs();
return;
}
if (object->type != OB_ARMATURE) {
params.set_default_remaining_outputs();
params.error_message_add(NodeWarningType::Error, TIP_("Object is not an armature"));
return;
}
const std::string bone_name = params.extract_input<std::string>("Bone Name"_ustr);
if (bone_name.empty()) {
params.set_default_remaining_outputs();
return;
}
if (!object->pose) {
params.set_default_remaining_outputs();
params.error_message_add(NodeWarningType::Error, TIP_("Object has no pose"));
return;
}
const bool transform_space_relative = (params.node().custom1 ==
GEO_NODE_TRANSFORM_SPACE_RELATIVE);
float4x4 geometry_transform = float4x4::identity();
if (transform_space_relative) {
const Object *self_object = params.self_object();
const bool self_transform_evaluated = DEG_object_transform_is_evaluated(*self_object);
const bool object_transform_evaluated = DEG_object_transform_is_evaluated(*object);
if (!self_transform_evaluated || !object_transform_evaluated) {
params.error_message_add(
NodeWarningType::Error,
TIP_("Cannot access object's transforms because it's not evaluated yet. "
"This can happen when there is a dependency cycle"));
params.set_default_remaining_outputs();
return;
}
geometry_transform = self_object->world_to_object() * object->object_to_world();
}
const bPoseChannel *pchan = BKE_pose_channel_find_name(object->pose, bone_name.c_str());
if (!pchan) {
params.set_default_remaining_outputs();
if (!params.output_is_required("Exists"_ustr)) {
params.error_message_add(
NodeWarningType::Error,
fmt::format(fmt::runtime(TIP_("Bone \"{}\" not found")), bone_name));
}
return;
}
const Bone *bone = pchan->bone_get(*object);
const float4x4 pose = geometry_transform * float4x4(pchan->pose_mat);
const float4x4 rest_pose = geometry_transform * float4x4(bone->arm_mat);
const float4x4 parent_pose = pchan->parent ? float4x4(pchan->parent->pose_mat) :
float4x4::identity();
const float4x4 parent_rest_pose = bone->parent ? float4x4(bone->parent->arm_mat) :
float4x4::identity();
const float4x4 local_pose = math::invert(rest_pose) * parent_rest_pose *
math::invert(parent_pose) * pose;
float4x4 transform_pose;
BKE_pchan_to_mat4({pchan, bone}, transform_pose.ptr());
params.set_output("Pose"_ustr, pose);
params.set_output("Local Pose"_ustr, local_pose);
params.set_output("Transform Pose"_ustr, transform_pose);
params.set_output("Rest Pose"_ustr, rest_pose);
params.set_output("Rest Length"_ustr, bone->length);
params.set_output("Exists"_ustr, true);
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem rna_node_geometry_object_info_transform_space_items[] = {
{GEO_NODE_TRANSFORM_SPACE_ORIGINAL,
"ORIGINAL",
0,
"Original",
"Output the bone pose relative to the armature object transform"},
{GEO_NODE_TRANSFORM_SPACE_RELATIVE,
"RELATIVE",
0,
"Relative",
"Bring the bone pose into the modified object"},
{0, nullptr, 0, nullptr, nullptr},
};
PropertyRNA *prop = RNA_def_node_enum(srna,
"transform_space",
"Transform Space",
"The transformation of the vector and geometry outputs",
rna_node_geometry_object_info_transform_space_items,
NOD_inline_enum_accessors(custom1),
GEO_NODE_TRANSFORM_SPACE_ORIGINAL);
RNA_def_property_update_runtime(prop, rna_Node_update_relations);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeBoneInfo"_ustr);
ntype.ui_name = "Bone Info";
ntype.ui_description = "Retrieve information of armature bones";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.initfunc = node_node_init;
ntype.draw_buttons = node_layout;
ntype.geometry_node_execute = node_geo_exec;
ntype.gather_link_search_ops = node_gather_link_search_ops;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_bone_info_cc

View File

@@ -0,0 +1,350 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_geometry_set_instances.hh"
#include "BKE_instances.hh"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "GEO_join_geometries.hh"
#include "GEO_mesh_boolean.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_boolean_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
const bNode *node = b.node_or_null();
auto &first_geometry = b.add_input<decl::Geometry>("Mesh 1"_ustr)
.only_realized_data()
.supported_type(GeometryComponent::Type::Mesh)
.description("Base mesh to subtract geometry from");
if (node != nullptr) {
switch (geometry::boolean::Operation(node->custom1)) {
case geometry::boolean::Operation::Intersect:
case geometry::boolean::Operation::Union:
b.add_input<decl::Geometry>("Mesh"_ustr, "Mesh 2"_ustr)
.supported_type(GeometryComponent::Type::Mesh)
.multi_input()
.description("Meshes to union or intersect");
break;
case geometry::boolean::Operation::Difference:
b.add_input<decl::Geometry>("Mesh 2"_ustr)
.supported_type(GeometryComponent::Type::Mesh)
.multi_input()
.description("Mesh that is subtracted from the first mesh");
break;
}
}
auto make_mesh_arr = [](bNode &node) {
node.custom2 = int16_t(geometry::boolean::Solver::MeshArr);
};
auto &self_intersect =
b.add_input<decl::Bool>("Self Intersection"_ustr).make_available(make_mesh_arr);
auto &hole_tolerant =
b.add_input<decl::Bool>("Hole Tolerant"_ustr).make_available(make_mesh_arr);
b.add_output<decl::Geometry>("Mesh"_ustr).propagate_all_geometry();
auto &output_edges = b.add_output<decl::Bool>("Intersecting Edges"_ustr)
.anonymous_attribute_output()
.make_available(make_mesh_arr);
if (node != nullptr) {
const auto operation = geometry::boolean::Operation(node->custom1);
const auto solver = geometry::boolean::Solver(node->custom2);
output_edges.available(
ELEM(solver, geometry::boolean::Solver::MeshArr, geometry::boolean::Solver::Manifold));
self_intersect.available(solver == geometry::boolean::Solver::MeshArr);
hole_tolerant.available(solver == geometry::boolean::Solver::MeshArr);
switch (operation) {
case geometry::boolean::Operation::Intersect:
case geometry::boolean::Operation::Union:
first_geometry.available(false);
break;
case geometry::boolean::Operation::Difference:
break;
}
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
layout.prop(ptr, "solver", UI_ITEM_NONE, "", ICON_NONE);
}
struct AttributeOutputs {
std::optional<std::string> intersecting_edges_id;
};
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = int16_t(geometry::boolean::Operation::Difference);
node->custom2 = int16_t(geometry::boolean::Solver::Float);
}
static Array<short> calc_mesh_material_map(const Mesh &mesh, VectorSet<Material *> &all_materials)
{
Array<short> map(mesh.totcol);
for (const int i : IndexRange(mesh.totcol)) {
Material *material = mesh.mat[i];
map[i] = material ? all_materials.index_of_or_add(material) : -1;
}
return map;
}
static void node_geo_exec(GeoNodeExecParams params)
{
geometry::boolean::Operation operation = geometry::boolean::Operation(params.node().custom1);
geometry::boolean::Solver solver = geometry::boolean::Solver(params.node().custom2);
bool use_self = false;
bool hole_tolerant = false;
if (solver == geometry::boolean::Solver::MeshArr) {
use_self = params.extract_input<bool>("Self Intersection"_ustr);
hole_tolerant = params.extract_input<bool>("Hole Tolerant"_ustr);
}
Vector<const Mesh *> meshes;
Vector<float4x4> transforms;
VectorSet<Material *> materials;
Vector<Array<short>> material_remaps;
GeometrySet set_a;
if (operation == geometry::boolean::Operation::Difference) {
set_a = params.extract_input<GeometrySet>("Mesh 1"_ustr);
/* Note that it technically wouldn't be necessary to realize the instances for the first
* geometry input, but the boolean code expects the first shape for the difference operation
* to be a single mesh. */
if (const Mesh *mesh_in_a = set_a.get_mesh()) {
meshes.append(mesh_in_a);
transforms.append(float4x4::identity());
if (mesh_in_a->totcol == 0) {
/* Necessary for faces using the default material when there are no material slots. */
materials.add(nullptr);
}
else {
materials.add_multiple({mesh_in_a->mat, mesh_in_a->totcol});
}
material_remaps.append({});
}
}
GeoNodesMultiInput<GeometrySet> geometry_sets =
params.extract_input<GeoNodesMultiInput<GeometrySet>>("Mesh 2"_ustr);
for (const GeometrySet &geometry : geometry_sets.values) {
if (const Mesh *mesh = geometry.get_mesh()) {
meshes.append(mesh);
transforms.append(float4x4::identity());
material_remaps.append(calc_mesh_material_map(*mesh, materials));
}
if (const bke::Instances *instances = geometry.get_instances()) {
const Span<bke::InstanceReference> references = instances->references();
const Span<int> handles = instances->reference_handles();
const Span<float4x4> instance_transforms = instances->transforms();
for (const int i : handles.index_range()) {
const bke::InstanceReference &reference = references[handles[i]];
switch (reference.type()) {
case bke::InstanceReference::Type::Object: {
const GeometrySet object_geometry = bke::object_get_evaluated_geometry_set(
reference.object());
if (const Mesh *mesh = object_geometry.get_mesh()) {
meshes.append(mesh);
transforms.append(instance_transforms[i]);
material_remaps.append(calc_mesh_material_map(*mesh, materials));
}
break;
}
case bke::InstanceReference::Type::GeometrySet: {
if (const Mesh *mesh = reference.geometry_set().get_mesh()) {
meshes.append(mesh);
transforms.append(instance_transforms[i]);
material_remaps.append(calc_mesh_material_map(*mesh, materials));
}
break;
}
case bke::InstanceReference::Type::None:
case bke::InstanceReference::Type::Collection:
break;
}
}
}
}
if (ELEM(solver, geometry::boolean::Solver::MeshArr, geometry::boolean::Solver::Manifold)) {
/* These solvers remap materials using realize_instances. */
material_remaps.resize(0);
}
AttributeOutputs attribute_outputs;
if (ELEM(solver, geometry::boolean::Solver::MeshArr, geometry::boolean::Solver::Manifold)) {
attribute_outputs.intersecting_edges_id = params.get_output_anonymous_attribute_id_if_needed(
"Intersecting Edges"_ustr);
}
Vector<int> intersecting_edges;
geometry::boolean::BooleanOpParameters op_params;
op_params.boolean_mode = operation;
op_params.no_self_intersections = !use_self;
op_params.watertight = !hole_tolerant;
op_params.no_nested_components = true; /* TODO: make this configurable. */
geometry::boolean::BooleanError error;
Mesh *result = geometry::boolean::mesh_boolean(
meshes,
transforms,
material_remaps,
op_params,
solver,
attribute_outputs.intersecting_edges_id ? &intersecting_edges : nullptr,
&error);
if (error.type == geometry::boolean::BooleanErrorType::NonManifold) {
if (!error.non_manifold_mesh_indices.is_empty()) {
for (const auto index : error.non_manifold_mesh_indices) {
params.error_message_add(
NodeWarningType::Error,
fmt::format(fmt::runtime(TIP_("Input {} was not manifold")), index));
}
}
else {
params.error_message_add(NodeWarningType::Error, TIP_("An input was not manifold"));
}
}
else if (error.type == geometry::boolean::BooleanErrorType::ResultTooBig) {
params.error_message_add(NodeWarningType::Error,
TIP_("Boolean result is too big for solver to handle"));
}
else if (error.type == geometry::boolean::BooleanErrorType::SolverNotAvailable) {
params.error_message_add(NodeWarningType::Error,
TIP_("Boolean solver not available (compiled without it)"));
}
else if (error.type == geometry::boolean::BooleanErrorType::UnknownError) {
params.error_message_add(NodeWarningType::Error, TIP_("Unknown Boolean error"));
}
if (!result) {
params.set_default_remaining_outputs();
return;
}
MEM_SAFE_DELETE(result->mat);
result->mat = MEM_new_array_uninitialized<Material *>(size_t(materials.size()), __func__);
result->totcol = materials.size();
MutableSpan(result->mat, result->totcol).copy_from(materials);
/* Store intersecting edges in attribute. */
if (attribute_outputs.intersecting_edges_id) {
MutableAttributeAccessor attributes = result->attributes_for_write();
SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_only_span<bool>(
*attribute_outputs.intersecting_edges_id, AttrDomain::Edge);
selection.span.fill(false);
for (const int i : intersecting_edges) {
selection.span[i] = true;
}
selection.finish();
}
geometry::debug_randomize_mesh_order(result);
Vector<GeometrySet> all_geometries;
all_geometries.append(set_a);
all_geometries.extend(geometry_sets.values);
const std::array types_to_join = {GeometryComponent::Type::Edit};
GeometrySet result_geometry = geometry::join_geometries(
all_geometries, {}, std::make_optional(types_to_join));
result_geometry.replace_mesh(result);
result_geometry.set_name(set_a.name());
for (const GeometrySet &geometry : all_geometries) {
result_geometry.merge_bundle_from(geometry);
}
params.set_output("Mesh"_ustr, std::move(result_geometry));
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem rna_node_geometry_boolean_method_items[] = {
{int(geometry::boolean::Operation::Intersect),
"INTERSECT",
0,
"Intersect",
"Keep the part of the mesh that is common between all operands"},
{int(geometry::boolean::Operation::Union),
"UNION",
0,
"Union",
"Combine meshes in an additive way"},
{int(geometry::boolean::Operation::Difference),
"DIFFERENCE",
0,
"Difference",
"Combine meshes in a subtractive way"},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem rna_geometry_boolean_solver_items[] = {
{int(geometry::boolean::Solver::MeshArr),
"EXACT",
0,
"Exact",
"Slower solver with the best results for coplanar faces"},
{int(geometry::boolean::Solver::Float),
"FLOAT",
0,
"Float",
"Simple solver with good performance, without support for overlapping geometry"},
{int(geometry::boolean::Solver::Manifold),
"MANIFOLD",
0,
"Manifold",
"Fastest solver that works only on manifold meshes but gives better results"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"operation",
"Operation",
"",
rna_node_geometry_boolean_method_items,
NOD_inline_enum_accessors(custom1),
int(geometry::boolean::Operation::Intersect));
RNA_def_node_enum(srna,
"solver",
"Solver",
"",
rna_geometry_boolean_solver_items,
NOD_inline_enum_accessors(custom2),
int(geometry::boolean::Solver::Float));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeMeshBoolean"_ustr, GEO_NODE_MESH_BOOLEAN);
ntype.ui_name = "Mesh Boolean";
ntype.ui_description = "Cut, subtract, or join multiple mesh inputs";
ntype.enum_name_legacy = "MESH_BOOLEAN";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.initfunc = node_init;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_boolean_cc

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GEO_foreach_geometry.hh"
#include "GEO_mesh_primitive_cuboid.hh"
#include "GEO_transform.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_bounding_box_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry"_ustr)
.description(
"Geometry to compute the bounding box of. Instances have to be realized before the full "
"bounding box can be computed");
b.add_input<decl::Bool>("Use Radius"_ustr)
.default_value(true)
.description(
"For curves, point clouds, and Grease Pencil, take the radius attribute into account "
"when computing the bounds.");
b.add_output<decl::Geometry>("Bounding Box"_ustr)
.propagate_all_geometry()
.description("A cube mesh enclosing the input geometry");
b.add_output<decl::Vector>("Min"_ustr);
b.add_output<decl::Vector>("Max"_ustr);
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"_ustr);
const bool use_radius = params.extract_input<bool>("Use Radius"_ustr);
/* Compute the min and max of all realized geometry for the two
* vector outputs, which are only meant to consider real geometry. */
const std::optional<Bounds<float3>> bounds = geometry_set.compute_boundbox_without_instances(
use_radius);
if (!bounds) {
params.set_output("Min"_ustr, float3(0));
params.set_output("Max"_ustr, float3(0));
}
else {
params.set_output("Min"_ustr, bounds->min);
params.set_output("Max"_ustr, bounds->max);
}
/* Generate the bounding box meshes inside each unique geometry set (including individually for
* every instance). Because geometry components are reference counted anyway, we can just
* repurpose the original geometry sets for the output. */
if (params.output_is_required("Bounding Box"_ustr)) {
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &sub_geometry) {
std::optional<Bounds<float3>> sub_bounds;
/* Reuse the min and max calculation if this is the main "real" geometry set. */
if (&sub_geometry == &geometry_set) {
sub_bounds = bounds;
}
else {
sub_bounds = sub_geometry.compute_boundbox_without_instances(use_radius);
}
if (!sub_bounds) {
sub_geometry.clear();
}
else {
const float3 scale = sub_bounds->max - sub_bounds->min;
const float3 center = sub_bounds->min + scale / 2.0f;
Mesh *mesh = geometry::create_cuboid_mesh(scale, 2, 2, 2, "uv_map");
geometry::transform_mesh(*mesh, center, math::Quaternion::identity(), float3(1));
sub_geometry.replace_mesh(mesh);
sub_geometry.keep_only({GeometryComponent::Type::Mesh, GeometryComponent::Type::Edit});
}
});
params.set_output("Bounding Box"_ustr, std::move(geometry_set));
}
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeBoundBox"_ustr, GEO_NODE_BOUNDING_BOX);
ntype.ui_name = "Bounding Box";
ntype.ui_description =
"Calculate the limits of a geometry's positions and generate a box mesh with those "
"dimensions";
ntype.enum_name_legacy = "BOUNDING_BOX";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_bounding_box_cc

View File

@@ -0,0 +1,234 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_camera_types.h"
#include "BKE_camera.h"
#include "BKE_scene.hh"
#include "DEG_depsgraph_query.hh"
#include "COM_node_operation.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_camera_info_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.add_output<decl::Matrix>("Projection Matrix"_ustr).description("Camera projection matrix");
b.add_output<decl::Float>("Focal Length"_ustr).description("Perspective camera focal length");
b.add_output<decl::Vector>("Sensor"_ustr).dimensions(2).description("Size of the camera sensor");
b.add_output<decl::Vector>("Shift"_ustr).dimensions(2).description("Camera shift");
b.add_output<decl::Float>("Clip Start"_ustr).description("Camera near clipping distance");
b.add_output<decl::Float>("Clip End"_ustr).description("Camera far clipping distance");
b.add_output<decl::Float>("Focus Distance"_ustr)
.description("Distance to the focus point for depth of field");
b.add_output<decl::Bool>("Is Orthographic"_ustr)
.description("Whether the camera is using orthographic projection");
b.add_output<decl::Float>("Orthographic Scale"_ustr)
.description("Orthographic camera scale (similar to zoom)");
b.add_input<decl::Object>("Camera"_ustr).optional_label();
}
static CameraParams get_camera_parameters(const Scene &scene, const Object &camera_object)
{
CameraParams camera_params;
BKE_camera_params_init(&camera_params);
BKE_camera_params_from_object(&camera_params, &camera_object);
BKE_camera_params_compute_viewplane(
&camera_params, scene.r.xsch, scene.r.ysch, scene.r.xasp, scene.r.yasp);
BKE_camera_params_compute_matrix(&camera_params);
return camera_params;
}
/* Computes the horizontal aspect ratio and effective sensor fit of the camera. */
static void compute_aspect_ratio_and_effective_sensor_fit(const Scene &scene,
const CameraParams &camera_parameters,
float &aspect_ratio,
eCamera_SensorFit &effective_sensor_fit)
{
int2 resolution;
BKE_render_resolution(&scene.r, false, &resolution.x, &resolution.y);
const float2 aspect_corrected_resolution = float2(resolution) *
float2(scene.r.xasp, scene.r.yasp);
aspect_ratio = aspect_corrected_resolution.x / aspect_corrected_resolution.y;
effective_sensor_fit = eCamera_SensorFit(BKE_camera_sensor_fit(
camera_parameters.sensor_fit, aspect_corrected_resolution.x, aspect_corrected_resolution.y));
}
static float2 compute_sensor_size(const Scene &scene, const CameraParams &camera_parameters)
{
float aspect_ratio;
eCamera_SensorFit effective_sensor_fit;
compute_aspect_ratio_and_effective_sensor_fit(
scene, camera_parameters, aspect_ratio, effective_sensor_fit);
const float sensor_size = BKE_camera_sensor_size(
camera_parameters.sensor_fit, camera_parameters.sensor_x, camera_parameters.sensor_y);
if (effective_sensor_fit == CAMERA_SENSOR_FIT_HOR) {
return float2(sensor_size, sensor_size / aspect_ratio);
}
return float2(sensor_size * aspect_ratio, sensor_size);
}
static float2 compute_lens_shift(const Scene &scene, const CameraParams &camera_parameters)
{
float aspect_ratio;
eCamera_SensorFit effective_sensor_fit;
compute_aspect_ratio_and_effective_sensor_fit(
scene, camera_parameters, aspect_ratio, effective_sensor_fit);
if (effective_sensor_fit == CAMERA_SENSOR_FIT_HOR) {
return float2(camera_parameters.shiftx, camera_parameters.shifty * aspect_ratio);
}
return float2(camera_parameters.shiftx / aspect_ratio, camera_parameters.shifty);
}
static void node_geo_exec(GeoNodeExecParams params)
{
const Scene *scene = DEG_get_evaluated_scene(params.depsgraph());
if (!scene) {
params.set_default_remaining_outputs();
return;
}
const Object *camera_obj = params.extract_input<Object *>("Camera"_ustr);
if (!camera_obj || camera_obj->type != OB_CAMERA) {
params.set_default_remaining_outputs();
return;
}
Camera *camera = id_cast<Camera *>(camera_obj->data);
if (!camera) {
params.set_default_remaining_outputs();
return;
}
const CameraParams camera_params = get_camera_parameters(*scene, *camera_obj);
const float4x4 projection_matrix(camera_params.winmat);
const float focus_distance = BKE_camera_object_dof_distance(camera_obj);
const float2 sensor_size = compute_sensor_size(*scene, camera_params);
const float2 lens_shift = compute_lens_shift(*scene, camera_params);
params.set_output("Projection Matrix"_ustr, projection_matrix);
params.set_output("Focal Length"_ustr, camera_params.lens);
params.set_output("Sensor"_ustr, float3{sensor_size, 0.0f});
params.set_output("Shift"_ustr, float3{lens_shift, 0.0f});
params.set_output("Clip Start"_ustr, camera_params.clip_start);
params.set_output("Clip End"_ustr, camera_params.clip_end);
params.set_output("Focus Distance"_ustr, focus_distance);
params.set_output("Is Orthographic"_ustr, camera_params.is_ortho);
params.set_output("Orthographic Scale"_ustr, camera_params.ortho_scale);
}
using namespace blender::compositor;
class CameraInfoOperation : public NodeOperation {
public:
using NodeOperation::NodeOperation;
void execute() override
{
const Object *camera_object = this->get_input("Camera").get_single_value<Object *>();
if (!camera_object || camera_object->type != OB_CAMERA) {
this->allocate_default_remaining_outputs();
return;
}
const Camera *camera = id_cast<Camera *>(camera_object->data);
if (!camera) {
this->allocate_default_remaining_outputs();
return;
}
const Scene &scene = this->context().get_scene();
const CameraParams camera_parameters = get_camera_parameters(scene, *camera_object);
Result &projection_matrix_result = this->get_result("Projection Matrix");
if (projection_matrix_result.should_compute()) {
projection_matrix_result.allocate_single_value();
projection_matrix_result.set_single_value(float4x4(camera_parameters.winmat));
}
Result &focal_length_result = this->get_result("Focal Length");
if (focal_length_result.should_compute()) {
focal_length_result.allocate_single_value();
focal_length_result.set_single_value(camera_parameters.lens);
}
const float2 sensor_size = compute_sensor_size(scene, camera_parameters);
Result &sensor_result = this->get_result("Sensor");
if (sensor_result.should_compute()) {
sensor_result.allocate_single_value();
sensor_result.set_single_value(sensor_size);
}
const float2 lens_shift = compute_lens_shift(scene, camera_parameters);
Result &shift_result = this->get_result("Shift");
if (shift_result.should_compute()) {
shift_result.allocate_single_value();
shift_result.set_single_value(lens_shift);
}
Result &clip_start_result = this->get_result("Clip Start");
if (clip_start_result.should_compute()) {
clip_start_result.allocate_single_value();
clip_start_result.set_single_value(camera_parameters.clip_start);
}
Result &clip_end_result = this->get_result("Clip End");
if (clip_end_result.should_compute()) {
clip_end_result.allocate_single_value();
clip_end_result.set_single_value(camera_parameters.clip_end);
}
Result &focus_distance_result = this->get_result("Focus Distance");
if (focus_distance_result.should_compute()) {
focus_distance_result.allocate_single_value();
const float focus_distance = BKE_camera_object_dof_distance(camera_object);
focus_distance_result.set_single_value(focus_distance);
}
Result &is_orthographic_result = this->get_result("Is Orthographic");
if (is_orthographic_result.should_compute()) {
is_orthographic_result.allocate_single_value();
is_orthographic_result.set_single_value(camera_parameters.is_ortho);
}
Result &orthographic_scale_result = this->get_result("Orthographic Scale");
if (orthographic_scale_result.should_compute()) {
orthographic_scale_result.allocate_single_value();
orthographic_scale_result.set_single_value(camera_parameters.ortho_scale);
}
}
};
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
{
return new CameraInfoOperation(context, node);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_cmp_node_type_base(&ntype, "GeometryNodeCameraInfo"_ustr);
ntype.ui_name = "Camera Info";
ntype.ui_description = "Retrieve information from a camera object";
ntype.nclass = NODE_CLASS_INPUT;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
ntype.get_compositor_operation = get_compositor_operation;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_camera_info_cc

View File

@@ -0,0 +1,323 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "BLI_string_utf8.h"
#include "BKE_idprop.hh"
#include "NOD_geo_closure.hh"
#include "NOD_socket_items_blend.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "NOD_socket_search_link.hh"
#include "NOD_sync_sockets.hh"
#include "BLO_read_write.hh"
#include "shader/node_shader_util.hh"
namespace blender {
namespace nodes::node_geo_closure_cc {
/** Shared between closure input and output node. */
static void node_layout_ex(ui::Layout &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_id) {
return;
}
bNode &output_node = const_cast<bNode &>(*zone->output_node());
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
PointerRNA output_node_ptr = RNA_pointer_create_discrete(&ntree.id, RNA_Node, &output_node);
layout.op("node.sockets_sync", IFACE_("Sync"), ICON_FILE_REFRESH);
layout.prop(&output_node_ptr, "define_signature", UI_ITEM_NONE, std::nullopt, ICON_NONE);
if (current_node->type_legacy == NODE_CLOSURE_INPUT) {
if (ui::Layout *panel = layout.panel(C, "input_items", false, IFACE_("Input Items"))) {
socket_items::ui::draw_items_list_with_operators<ClosureInputItemsAccessor>(
C, panel, ntree, output_node);
socket_items::ui::draw_active_item_props<ClosureInputItemsAccessor>(
ntree, output_node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
panel->prop(item_ptr, "structure_type", UI_ITEM_NONE, IFACE_("Shape"), ICON_NONE);
});
}
}
else {
if (ui::Layout *panel = layout.panel(C, "output_items", false, IFACE_("Output Items"))) {
socket_items::ui::draw_items_list_with_operators<ClosureOutputItemsAccessor>(
C, panel, ntree, output_node);
socket_items::ui::draw_active_item_props<ClosureOutputItemsAccessor>(
ntree, output_node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
panel->prop(item_ptr, "structure_type", UI_ITEM_NONE, IFACE_("Shape"), ICON_NONE);
});
}
}
}
namespace input_node {
NODE_STORAGE_FUNCS(NodeClosureInput);
static void node_declare(NodeDeclarationBuilder &b)
{
const bNode *node = b.node_or_null();
const bNodeTree *tree = b.tree_or_null();
if (node && tree) {
const NodeClosureInput &storage = node_storage(*node);
const bNode *output_node = tree->node_by_id(storage.output_node_id);
if (output_node) {
const auto &output_storage = *static_cast<const NodeClosureOutput *>(output_node->storage);
for (const int i : IndexRange(output_storage.input_items.items_num)) {
const NodeClosureInputItem &item = output_storage.input_items.items[i];
const eNodeSocketDatatype socket_type = item.socket_type;
const UString identifier(ClosureInputItemsAccessor::socket_identifier_for_item(item));
auto &decl = b.add_output(socket_type, UString(item.name), identifier);
decl.socket_name_ptr(&tree->id, *ClosureInputItemsAccessor::item_srna, &item, "name");
if (item.structure_type != NodeSocketInterfaceStructureType::Auto) {
decl.structure_type(StructureType(item.structure_type));
}
else {
decl.structure_type(StructureType::Dynamic);
}
}
}
}
b.add_output<decl::Extend>(""_ustr, "__extend__"_ustr)
.custom_draw(socket_items::ui::draw_extend_socket_fn<ClosureInputItemsAccessor>());
}
static void node_label(const bNodeTree * /*ntree*/,
const bNode * /*node*/,
char *label,
const int label_maxncpy)
{
BLI_strncpy_utf8(label, CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, "Closure"), label_maxncpy);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeClosureInput *data = MEM_new<NodeClosureInput>(__func__);
node->storage = data;
}
static bool node_insert_link(bke::NodeInsertLinkParams &params)
{
bNode *output_node = params.ntree.node_by_id(node_storage(params.node).output_node_id);
if (!output_node) {
return true;
}
return socket_items::try_add_item_via_any_extend_socket<ClosureInputItemsAccessor>(
params.ntree, params.node, *output_node, params.link);
}
static void node_register()
{
static bke::bNodeType ntype;
sh_geo_node_type_base(&ntype, "NodeClosureInput"_ustr, NODE_CLOSURE_INPUT);
ntype.ui_name = "Closure Input";
ntype.nclass = NODE_CLASS_INTERFACE;
ntype.declare = node_declare;
ntype.gather_link_search_ops = nullptr;
ntype.initfunc = node_init;
ntype.labelfunc = node_label;
ntype.no_muting = true;
ntype.insert_link = node_insert_link;
ntype.draw_buttons_ex = node_layout_ex;
bke::node_type_storage(
ntype, "NodeClosureInput", node_free_standard_storage, node_copy_standard_storage);
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace input_node
namespace output_node {
NODE_STORAGE_FUNCS(NodeClosureOutput);
static void node_declare(NodeDeclarationBuilder &b)
{
const bNodeTree *tree = b.tree_or_null();
const bNode *node = b.node_or_null();
if (node && tree) {
const NodeClosureOutput &storage = node_storage(*node);
for (const int i : IndexRange(storage.output_items.items_num)) {
const NodeClosureOutputItem &item = storage.output_items.items[i];
const eNodeSocketDatatype socket_type = item.socket_type;
const UString identifier(ClosureOutputItemsAccessor::socket_identifier_for_item(item));
auto &decl = b.add_input(socket_type, UString(item.name), identifier);
decl.socket_name_ptr(&tree->id, *ClosureOutputItemsAccessor::item_srna, &item, "name");
if (item.structure_type != NodeSocketInterfaceStructureType::Auto) {
decl.structure_type(StructureType(item.structure_type));
}
else {
decl.structure_type(StructureType::Dynamic);
}
}
}
b.add_input<decl::Extend>(""_ustr, "__extend__"_ustr)
.custom_draw(socket_items::ui::draw_extend_socket_fn<ClosureOutputItemsAccessor>());
b.add_output<decl::Closure>("Closure"_ustr);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeClosureOutput *data = MEM_new<NodeClosureOutput>(__func__);
node->storage = data;
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeClosureOutput &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_new<NodeClosureOutput>(__func__, dna::shallow_copy(src_storage));
dst_node->storage = dst_storage;
socket_items::copy_array<ClosureInputItemsAccessor>(*src_node, *dst_node);
socket_items::copy_array<ClosureOutputItemsAccessor>(*src_node, *dst_node);
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<ClosureInputItemsAccessor>(*node);
socket_items::destruct_array<ClosureOutputItemsAccessor>(*node);
MEM_delete(static_cast<NodeClosureOutput *>(node->storage));
}
static bool node_insert_link(bke::NodeInsertLinkParams &params)
{
if (params.C && params.link.fromnode == &params.node && params.link.tosock->type == SOCK_CLOSURE)
{
const NodeClosureOutput &storage = node_storage(params.node);
if (storage.input_items.items_num == 0 && storage.output_items.items_num == 0) {
SpaceNode *snode = CTX_wm_space_node(params.C);
if (snode && snode->edittree == &params.ntree) {
bNode *input_node = bke::zone_type_by_node_type(NODE_CLOSURE_OUTPUT)
->get_corresponding_input(params.ntree, params.node);
if (input_node) {
sync_sockets_closure(*snode, *input_node, params.node, nullptr, params.link.tosock);
}
}
}
return true;
}
return socket_items::try_add_item_via_any_extend_socket<ClosureOutputItemsAccessor>(
params.ntree, params.node, params.node, params.link);
}
static void node_operators()
{
socket_items::ops::make_common_operators<ClosureInputItemsAccessor>();
socket_items::ops::make_common_operators<ClosureOutputItemsAccessor>();
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const bNodeSocket &other_socket = params.other_socket();
if (other_socket.type != SOCK_CLOSURE) {
return;
}
if (other_socket.in_out == SOCK_OUT) {
return;
}
params.add_item_full_name(IFACE_("Closure"), [](LinkSearchOpParams &params) {
bNode &input_node = params.add_node("NodeClosureInput"_ustr);
bNode &output_node = params.add_node("NodeClosureOutput"_ustr);
output_node.location[0] = 300;
auto &input_storage = *static_cast<NodeClosureInput *>(input_node.storage);
input_storage.output_node_id = output_node.identifier;
params.connect_available_socket(output_node, "Closure"_ustr);
SpaceNode &snode = *CTX_wm_space_node(&params.C);
sync_sockets_closure(snode, input_node, output_node, nullptr);
});
}
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
{
socket_items::blend_write<ClosureInputItemsAccessor>(&writer, node);
socket_items::blend_write<ClosureOutputItemsAccessor>(&writer, node);
}
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
{
socket_items::blend_read_data<ClosureInputItemsAccessor>(&reader, node);
socket_items::blend_read_data<ClosureOutputItemsAccessor>(&reader, node);
}
static void node_register()
{
static bke::bNodeType ntype;
sh_geo_node_type_base(&ntype, "NodeClosureOutput"_ustr, NODE_CLOSURE_OUTPUT);
ntype.ui_name = "Closure Output";
ntype.nclass = NODE_CLASS_INTERFACE;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.labelfunc = input_node::node_label;
ntype.no_muting = true;
ntype.register_operators = node_operators;
ntype.gather_link_search_ops = node_gather_link_searches;
ntype.insert_link = node_insert_link;
ntype.draw_buttons_ex = node_layout_ex;
ntype.blend_write_storage_content = node_blend_write;
ntype.blend_data_read_storage_content = node_blend_read;
bke::node_type_storage(ntype, "NodeClosureOutput", node_free_storage, node_copy_storage);
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace output_node
} // namespace nodes::node_geo_closure_cc
namespace nodes {
StructRNA **ClosureInputItemsAccessor::item_srna = &RNA_NodeClosureInputItem;
void ClosureInputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
writer->write_string(item.name);
}
void ClosureInputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
StructRNA **ClosureOutputItemsAccessor::item_srna = &RNA_NodeClosureOutputItem;
void ClosureOutputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
writer->write_string(item.name);
}
void ClosureOutputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
} // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,309 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_compute_contexts.hh"
#include "BKE_volume_grid.hh"
#include "BLO_read_write.hh"
#include "NOD_geo_closure_to_list.hh"
#include "NOD_geometry_nodes_closure_eval.hh"
#include "NOD_geometry_nodes_closure_location.hh"
#include "NOD_geometry_nodes_list.hh"
#include "NOD_socket_items_blend.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_prototypes.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_closure_to_list_cc {
NODE_STORAGE_FUNCS(GeometryNodeClosureToList)
using ItemsAccessor = ClosureToListItemsAccessor;
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
const bNodeTree *tree = b.tree_or_null();
const bNode *node = b.node_or_null();
if (!node) {
return;
}
const GeometryNodeClosureToList &storage = node_storage(*node);
const Span<GeometryNodeClosureToListItem> items(storage.items, storage.items_num);
for (const int i : items.index_range()) {
const GeometryNodeClosureToListItem &item = items[i];
const UString output_identifier{ItemsAccessor::output_socket_identifier_for_item(item)};
const UString name{item.name};
const eNodeSocketDatatype type = item.socket_type;
b.add_output(type, name, output_identifier)
.structure_type(StructureType::List)
.socket_name_ptr(&tree->id, *ItemsAccessor::item_srna, &item, "name")
.propagate_all()
.references_other_outputs();
}
b.add_output<decl::Extend>(""_ustr, "__extend__"_ustr)
.structure_type(StructureType::List)
.custom_draw(socket_items::ui::draw_extend_socket_fn<ItemsAccessor>());
b.add_input<decl::Int>("Count"_ustr)
.default_value(1)
.min(0)
.description("The number of elements in the list");
b.add_input<decl::Closure>("Closure"_ustr).create_signature([](const bNode &node) {
return ClosureSignature::from_closure_to_list_node(node);
});
}
static void node_layout_ex(ui::Layout &layout, bContext *C, PointerRNA *ptr)
{
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode &node = *static_cast<bNode *>(ptr->data);
if (ui::Layout *panel = layout.panel(C, "closure_to_list_items", false, IFACE_("Items"))) {
socket_items::ui::draw_items_list_with_operators<ItemsAccessor>(C, panel, tree, node);
socket_items::ui::draw_active_item_props<ItemsAccessor>(tree, node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
panel->prop(item_ptr, "structure_type", UI_ITEM_NONE, IFACE_("Shape"), ICON_NONE);
});
}
}
static void node_gather_link_search_ops(GatherLinkSearchOpParams &params)
{
const eNodeSocketDatatype data_type = params.other_socket().type;
if (params.in_out() == SOCK_IN) {
if (params.node_tree().typeinfo->validate_link(data_type, SOCK_INT)) {
params.add_item(IFACE_("Count"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeClosureToList"_ustr);
params.update_and_connect_available_socket(node, "Count"_ustr);
});
}
if (params.node_tree().typeinfo->validate_link(data_type, SOCK_CLOSURE)) {
params.add_item(IFACE_("Closure"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeClosureToList"_ustr);
params.update_and_connect_available_socket(node, "Closure"_ustr);
});
}
}
else {
params.add_item(IFACE_("List"), [data_type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeClosureToList"_ustr);
socket_items::add_item_with_socket_type_and_name<ItemsAccessor>(
params.node_tree, node, data_type, params.socket.name);
params.update_and_connect_available_socket(node, UString(params.socket.name));
});
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
const int count = params.extract_input<int>("Count"_ustr);
if (count < 0) {
params.error_message_add(NodeWarningType::Error, "Count must not be negative");
params.set_default_remaining_outputs();
return;
}
const bNode &node = params.node();
const GeometryNodeClosureToList &storage = node_storage(node);
const Span<GeometryNodeClosureToListItem> items(storage.items, storage.items_num);
ClosurePtr closure = params.extract_input<ClosurePtr>("Closure"_ustr);
if (!closure) {
params.set_default_remaining_outputs();
return;
}
Vector<int> required_items;
for (const int i : items.index_range()) {
if (params.output_is_required(
UString(ItemsAccessor::output_socket_identifier_for_item(items[i]))))
{
required_items.append(i);
}
}
Array<const bke::bNodeSocketType *> socket_types(required_items.size());
for (const int required_i : required_items.index_range()) {
const int item_i = required_items[required_i];
const eNodeSocketDatatype type = items[item_i].socket_type;
socket_types[required_i] = bke::node_socket_type_find_static(type);
}
if (socket_types.as_span().contains(nullptr)) {
params.set_default_remaining_outputs();
return;
}
Array<Array<bke::SocketValueVariant>> closure_results(required_items.size(), NoInitialization());
for (const int i : closure_results.index_range()) {
new (&closure_results[i]) Array<bke::SocketValueVariant>(count, NoInitialization());
}
const GeoNodesUserData &parent_user_data = *params.user_data();
/* The grain size is completely arbitrary since we don't know how expensive the closure is.
* However since the closure evaluation itself has fairly high overhead, it makes to optimize for
* the case where each task has a relatively high cost. */
const bke::bNodeSocketType *int_type = bke::node_socket_type_find("NodeSocketInt");
threading::parallel_for(IndexRange(count), 8, [&](const IndexRange range) {
ClosureEagerEvalParams closure_params;
/* Create inputs. */
closure_params.inputs.resize(1);
closure_params.inputs[0].key = "Index";
closure_params.inputs[0].type = int_type;
/* Create outputs. */
closure_params.outputs.resize(required_items.size());
for (const int required_i : required_items.index_range()) {
const int item_i = required_items[required_i];
closure_params.outputs[required_i].key = items[item_i].name;
closure_params.outputs[required_i].type = socket_types[required_i];
}
for (const int64_t list_i : range) {
/* Create input value. */
BLI_assert(list_i < std::numeric_limits<int>::max());
closure_params.inputs[0].value = bke::SocketValueVariant::From(int(list_i));
/* Set output locations. */
for (const int required_i : required_items.index_range()) {
closure_params.outputs[required_i].value = &closure_results[required_i][list_i];
}
const bke::ClosureToListComputeContext context(
parent_user_data.compute_context, node.identifier, int(list_i));
GeoNodesUserData user_data = parent_user_data;
user_data.compute_context = &context;
user_data.verbose_log = should_log_verbose_in_context(user_data, context.hash());
closure_params.user_data = &user_data;
evaluate_closure_eagerly(*closure, closure_params);
}
});
for (const int required_i : required_items.index_range()) {
const int item_i = required_items[required_i];
const UString identifier{ItemsAccessor::output_socket_identifier_for_item(items[item_i])};
Array<bke::SocketValueVariant> &values = closure_results[required_i];
if (std::all_of(values.begin(), values.end(), [](const bke::SocketValueVariant &value) {
return value.is_single();
}))
{
const eNodeSocketDatatype socket_type = items[item_i].socket_type;
const CPPType &type = *bke::socket_type_to_geo_nodes_base_cpp_type(socket_type);
GArray<> array(type, count, NoInitialization());
threading::parallel_for(IndexRange(count), 128, [&](const IndexRange range) {
for (const int list_i : range) {
void *closure_result = const_cast<void *>(values[list_i].get_single_ptr_raw());
type.move_construct(closure_result, array[list_i]);
}
});
params.set_output(identifier, GList::from_garray(std::move(array)));
}
else {
params.set_output(identifier, GList::from_container(std::move(values)));
}
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->storage = MEM_new<GeometryNodeClosureToList>(__func__);
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<ItemsAccessor>(*node);
MEM_delete(static_cast<GeometryNodeClosureToList *>(node->storage));
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const GeometryNodeClosureToList &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_new<GeometryNodeClosureToList>(__func__, src_storage);
dst_node->storage = dst_storage;
socket_items::copy_array<ItemsAccessor>(*src_node, *dst_node);
}
static void node_operators()
{
socket_items::ops::make_common_operators<ItemsAccessor>();
}
static bool node_insert_link(bke::NodeInsertLinkParams &params)
{
return socket_items::try_add_item_via_any_extend_socket<ItemsAccessor>(
params.ntree, params.node, params.node, params.link);
}
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
{
socket_items::blend_write<ItemsAccessor>(&writer, node);
}
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
{
socket_items::blend_read_data<ItemsAccessor>(&reader, node);
}
static const bNodeSocket *node_internally_linked_input(const bNodeTree & /*tree*/,
const bNode &node,
const bNodeSocket &output_socket)
{
return node.input_by_identifier(output_socket.identifier_ustr());
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeClosureToList"_ustr);
ntype.ui_name = "Closure to List";
ntype.ui_description = "Create a list of values";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
bke::node_type_storage(ntype, "GeometryNodeClosureToList", node_free_storage, node_copy_storage);
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons_ex = node_layout_ex;
ntype.register_operators = node_operators;
ntype.insert_link = node_insert_link;
ntype.ignore_inferred_input_socket_visibility = true;
ntype.gather_link_search_ops = node_gather_link_search_ops;
ntype.internally_linked_input = node_internally_linked_input;
ntype.blend_write_storage_content = node_blend_write;
ntype.blend_data_read_storage_content = node_blend_read;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_closure_to_list_cc
namespace blender::nodes {
StructRNA **ClosureToListItemsAccessor::item_srna = &RNA_GeometryNodeClosureToListItem;
void ClosureToListItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
writer->write_string(item.name);
}
void ClosureToListItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
} // namespace blender::nodes

View File

@@ -0,0 +1,169 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_disjoint_set.hh"
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
#include "BKE_geometry_fields.hh"
#include "BKE_mesh.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_input_cluster_by_connected_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.structure_type(StructureType::Field);
b.add_input<decl::Vector>("Position"_ustr)
.default_input_type(NODE_DEFAULT_INPUT_POSITION_FIELD)
.structure_type(StructureType::Field);
b.add_input<decl::Float>("Distance"_ustr).default_value(0.001f).min(0.0f).subtype(PROP_DISTANCE);
b.add_output<decl::Int>("Cluster ID"_ustr)
.structure_type(StructureType::Field)
.propagate_references();
}
class ClusterByConnectedFieldInput final : public bke::GeometryFieldInput {
private:
Field<bool> selection_field_;
Field<float3> position_field_;
float min_distance_;
public:
ClusterByConnectedFieldInput(Field<bool> selection_field,
Field<float3> position_field,
float min_distance)
: bke::GeometryFieldInput(CPPType::get<int>(), "Cluster by Connected"),
selection_field_(std::move(selection_field)),
position_field_(std::move(position_field)),
min_distance_(min_distance)
{
}
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
const IndexMask &mask) const override
{
if (context.type() != bke::GeometryComponent::Type::Mesh) {
return fn::IndexFieldInput::get_index_varray(mask);
}
const Mesh &mesh = *context.mesh();
const Span<int2> edges = mesh.edges();
const bke::MeshFieldContext edge_context(mesh, AttrDomain::Edge);
fn::FieldEvaluator edge_evaluator(edge_context, mesh.edges_num);
edge_evaluator.set_selection(selection_field_);
edge_evaluator.evaluate();
const IndexMask selection = edge_evaluator.get_evaluated_selection_as_mask();
if (selection.is_empty()) {
return fn::IndexFieldInput::get_index_varray(mask);
}
const bke::MeshFieldContext vert_context(mesh, AttrDomain::Point);
fn::FieldEvaluator point_evaluator(vert_context, mesh.verts_num);
point_evaluator.add(position_field_);
point_evaluator.evaluate();
const VArraySpan<float3> position = point_evaluator.get_evaluated<float3>(0);
DisjointSet<int> vertex_cluster(mesh.verts_num);
Array<float3> vert_cluster_center(mesh.verts_num);
array_utils::copy(position, vert_cluster_center.as_mutable_span());
Array<int> vert_cluster_size(mesh.verts_num, 1);
selection.foreach_index([&](const int edge_i) {
const int2 edge = edges[edge_i];
const int2 edge_clusters(vertex_cluster.find_root(edge[0]),
vertex_cluster.find_root(edge[1]));
if (edge_clusters[0] == edge_clusters[1]) {
return;
}
const float3 vert_a_cluster = vert_cluster_center[edge_clusters[0]];
const float3 vert_b_cluster = vert_cluster_center[edge_clusters[1]];
const float distance = math::distance(vert_a_cluster, vert_b_cluster);
if (distance >= min_distance_) {
return;
}
/* Use original vertices instead of already found roots to keep path folding optimization. */
const int new_cluster_root = vertex_cluster.join(edge[0], edge[1]);
const int new_cluster_size = vert_cluster_size[edge_clusters[0]] +
vert_cluster_size[edge_clusters[1]];
const float3 new_cluster_center = math::interpolate(vert_a_cluster,
vert_b_cluster,
vert_cluster_size[edge_clusters[1]] /
float(new_cluster_size));
vert_cluster_center[new_cluster_root] = new_cluster_center;
vert_cluster_size[new_cluster_root] = new_cluster_size;
});
Array<int> cluster_indices(mesh.verts_num);
threading::parallel_for(IndexRange(mesh.verts_num), 1024, [&](const IndexRange range) {
for (const int i : range) {
cluster_indices[i] = std::as_const(vertex_cluster).find_root(i);
}
});
return mesh.attributes().adapt_domain<int>(
VArray<int>::from_container(std::move(cluster_indices)),
AttrDomain::Point,
context.domain());
}
void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const override
{
fn(selection_field_);
fn(position_field_);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(selection_field_));
hash.add(deep_hash_cache.ensure(position_field_));
hash.add(min_distance_);
}
std::optional<AttrDomain> preferred_domain(
const GeometryComponent & /*component*/) const override
{
return AttrDomain::Point;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
params.set_output("Cluster ID"_ustr,
Field<int>::from_input<ClusterByConnectedFieldInput>(
params.extract_input<Field<bool>>("Selection"_ustr),
params.extract_input<Field<float3>>("Position"_ustr),
params.extract_input<float>("Distance"_ustr)));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeClusterByConnected"_ustr);
ntype.ui_name = "Cluster by Connected";
ntype.ui_description =
"Group mesh vertices connected by edges when they are within a specified distance";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.default_width = bke::NodeWidth::_160;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_input_cluster_by_connected_cc

View File

@@ -0,0 +1,227 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_array_utils.hh"
#include "BLI_index_mask.hh"
#include "BLI_kdtree.hh"
#include "BKE_geometry_fields.hh"
#include "atomic_ops.h"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_cluster_by_distance_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.structure_type(StructureType::Field)
.hide_value();
b.add_input<decl::Int>("Group ID"_ustr).structure_type(StructureType::Field).hide_value();
b.add_input<decl::Vector>("Position"_ustr)
.default_input_type(NODE_DEFAULT_INPUT_POSITION_FIELD)
.structure_type(StructureType::Field);
b.add_input<decl::Float>("Distance"_ustr).default_value(0.001f).min(0.0f).subtype(PROP_DISTANCE);
b.add_output<decl::Int>("Cluster ID"_ustr)
.structure_type(StructureType::Field)
.propagate_references();
}
constexpr int NO_CLUSTER_VALUE = -1;
static void set_no_cluster_value(MutableSpan<int> r_cluster_ids, const IndexMask &mask)
{
mask.foreach_index_optimized<int>(
[&](const int i) {
if (r_cluster_ids[i] == NO_CLUSTER_VALUE) {
r_cluster_ids[i] = i;
}
},
exec_mode::grain_size(4096));
}
class ClusterByDistanceFieldInput final : public bke::GeometryFieldInput {
private:
Field<float3> positions_field_;
Field<int> group_field_;
Field<bool> selection_field_;
float distance_;
public:
ClusterByDistanceFieldInput(Field<float3> positions_field,
Field<int> group_field,
Field<bool> selection_field,
const float distance)
: bke::GeometryFieldInput(CPPType::get<int>(), "Cluster by Distance"),
positions_field_(std::move(positions_field)),
group_field_(std::move(group_field)),
selection_field_(std::move(selection_field)),
distance_(distance)
{
}
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
const IndexMask &mask) const final
{
if (!context.attributes()) {
return {};
}
const int domain_size = context.attributes()->domain_size(context.domain());
fn::FieldEvaluator evaluator{context, domain_size};
evaluator.add(positions_field_);
evaluator.add(group_field_);
evaluator.set_selection(selection_field_);
evaluator.evaluate();
const VArraySpan<float3> positions = evaluator.get_evaluated<float3>(0);
const VArray<int> group_ids = evaluator.get_evaluated<int>(1);
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
IndexMaskMemory memory;
/* With context info about mask to compute we can skip processing of the rest of the values.
* But in current case this will affect result values since some cluster might have lowest ID
* of element outside of visible mask. */
const IndexMask mask_to_cluster = IndexMask::from_intersection(mask, selection, memory);
if (mask_to_cluster.is_empty()) {
return fn::IndexFieldInput::get_index_varray(mask);
}
Array<int> cluster_ids(mask.min_array_size());
const IndexMask mask_to_fallback = IndexMask::from_difference(mask, mask_to_cluster, memory);
array_utils::fill_index_range<int>(mask_to_fallback, cluster_ids);
std::optional<VArraySpan<int>> group_id_span;
const auto group_indices = [&]() -> VectorSet<int> {
if (group_ids.is_single()) {
return {group_ids.get_internal_single()};
}
VectorSet<int> group_indices;
group_id_span.emplace(group_ids);
mask_to_cluster.foreach_index_optimized<int>(
[&](const int i) { group_indices.add((*group_id_span)[i]); });
return group_indices;
}();
const int groups_num = group_indices.size();
if (groups_num == 1) {
KDTree<float3> *tree = kdtree_new<float3>(mask_to_cluster.size());
mask_to_cluster.foreach_index(
[&](const int i) { kdtree_insert<float3>(tree, i, positions[i]); });
kdtree_balance<float3>(tree);
index_mask::masked_fill<int>(cluster_ids, NO_CLUSTER_VALUE, mask_to_cluster);
kdtree_calc_duplicates_fast<float3>(tree, distance_, true, cluster_ids.data());
kdtree_free<float3>(tree);
set_no_cluster_value(cluster_ids, mask_to_cluster);
return VArray<int>::from_container(std::move(cluster_ids));
}
Array<int> group_offset_data(groups_num + 1, 0);
mask_to_cluster.foreach_index_optimized<int>(
[&](const int i) {
const int group_i = group_indices.index_of((*group_id_span)[i]);
atomic_add_and_fetch_int32(&group_offset_data[group_i], 1);
},
exec_mode::grain_size(8192));
const OffsetIndices<int> group_offsets = offset_indices::accumulate_counts_to_offsets(
group_offset_data);
Array<int> indices_by_group(group_offsets.total_size());
Array<int> group_counts(groups_num, 0);
mask_to_cluster.foreach_index_optimized<int>(
[&](const int i) {
const int group_i = group_indices.index_of((*group_id_span)[i]);
const int index_in_group = atomic_fetch_and_add_int32(&group_counts[group_i], 1);
indices_by_group[group_offsets[group_i][index_in_group]] = int(i);
},
exec_mode::grain_size(8192));
offset_indices::sort_groups(group_offsets, indices_by_group);
threading::parallel_for(
IndexRange(groups_num),
1024,
[&](const IndexRange range) {
Vector<int, 64> group_cluster_ids;
for (const int group_i : range) {
const Span group = indices_by_group.as_span().slice(group_offsets[group_i]);
group_cluster_ids.resize(group.size());
group_cluster_ids.fill(NO_CLUSTER_VALUE);
KDTree<float3> *tree = kdtree_new<float3>(group.size());
for (const int pos : group.index_range()) {
kdtree_insert<float3>(tree, pos, positions[group[pos]]);
}
kdtree_balance<float3>(tree);
kdtree_calc_duplicates_fast<float3>(tree, distance_, true, group_cluster_ids.data());
kdtree_free<float3>(tree);
set_no_cluster_value(group_cluster_ids, group_cluster_ids.index_range());
threading::parallel_for(group.index_range(), 4096, [&](const IndexRange range) {
for (const int pos : range) {
const int i = group[pos];
cluster_ids[i] = group[group_cluster_ids[pos]];
}
});
}
},
threading::accumulated_task_sizes(
[&](const IndexRange range) { return group_offsets[range].size(); }));
#ifndef NDEBUG
mask.foreach_index([&](const int i) { BLI_assert(cluster_ids[i] != NO_CLUSTER_VALUE); });
#endif
return VArray<int>::from_container(std::move(cluster_ids));
}
void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const override
{
fn(positions_field_);
fn(group_field_);
fn(selection_field_);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(positions_field_));
hash.add(deep_hash_cache.ensure(group_field_));
hash.add(deep_hash_cache.ensure(selection_field_));
hash.add(distance_);
}
std::optional<AttrDomain> preferred_domain(const GeometryComponent &component) const final
{
return bke::try_detect_field_domain(component, positions_field_);
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
params.set_output("Cluster ID"_ustr,
Field<int>::from_input<ClusterByDistanceFieldInput>(
params.extract_input<Field<float3>>("Position"_ustr),
params.extract_input<Field<int>>("Group ID"_ustr),
params.extract_input<Field<bool>>("Selection"_ustr),
params.extract_input<float>("Distance"_ustr)));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeClusterByDistance"_ustr);
ntype.ui_name = "Cluster by Distance";
ntype.ui_description = "Group elements into integer IDs based on proximity of vector values";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_cluster_by_distance_cc

View File

@@ -0,0 +1,115 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "DNA_collection_types.h"
#include "BKE_collection.hh"
#include "BKE_lib_id.hh"
#include "DEG_depsgraph_query.hh"
#include "NOD_geometry_nodes_list.hh"
#include "node_geometry_util.hh"
#include <algorithm>
namespace blender::nodes::node_geo_collection_children_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Collection>("Collection"_ustr).optional_label();
b.add_input<decl::Bool>("Recursive"_ustr)
.description("Recursively retrieve collections and objects");
b.add_output<decl::Collection>("Collections"_ustr).structure_type(StructureType::List);
b.add_output<decl::Object>("Objects"_ustr).structure_type(StructureType::List);
}
static void collection_children_recursive(Collection *collection,
Vector<Collection *> &collections,
Set<Collection *> &visited)
{
for (CollectionChild &child : collection->children) {
Collection *cc = child.collection;
if (visited.add(cc)) {
collections.append(cc);
collection_children_recursive(cc, collections, visited);
}
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
Collection *collection = params.extract_input<Collection *>("Collection"_ustr);
const bool recursive = params.extract_input<bool>("Recursive"_ustr);
if (collection == nullptr) {
params.set_default_remaining_outputs();
return;
}
Vector<Collection *> child_collections;
if (recursive) {
Set<Collection *> visited;
collection_children_recursive(collection, child_collections, visited);
}
else {
for (CollectionChild &child : collection->children) {
child_collections.append(child.collection);
}
}
std::ranges::sort(child_collections, [](const Collection *a, const Collection *b) {
return BLI_strcasecmp_natural(BKE_id_name(a->id), BKE_id_name(b->id)) < 0;
});
Vector<Object *> child_objects;
Vector<Collection *> obj_collections;
obj_collections.append(collection);
if (recursive) {
obj_collections.extend(child_collections);
}
params.set_output("Collections"_ustr, GList::from_container(std::move(child_collections)));
if (!params.output_is_required("Objects"_ustr)) {
params.set_default_remaining_outputs();
return;
}
Set<const Object *> obj_visited;
for (Collection *col : obj_collections) {
for (CollectionObject &cob : col->gobject) {
if (obj_visited.add(cob.ob)) {
child_objects.append(cob.ob);
}
}
}
std::ranges::sort(child_objects, [](const Object *a, const Object *b) {
return BLI_strcasecmp_natural(BKE_id_name(a->id), BKE_id_name(b->id)) < 0;
});
params.set_output("Objects"_ustr, GList::from_container(std::move(child_objects)));
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCollectionChildren"_ustr);
ntype.ui_name = "Collection Children";
ntype.ui_description =
"Retrieve a collection's object and collection children, in a name-based order";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_collection_children_cc

View File

@@ -0,0 +1,214 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "DNA_collection_types.h"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "BKE_collection.hh"
#include "BKE_instances.hh"
#include "DEG_depsgraph_query.hh"
#include "node_geometry_util.hh"
#include <algorithm>
namespace blender::nodes::node_geo_collection_info_cc {
NODE_STORAGE_FUNCS(NodeGeometryCollectionInfo)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Collection>("Collection"_ustr).optional_label();
b.add_input<decl::Bool>("Separate Children"_ustr)
.description(
"Output each child of the collection as a separate instance, sorted alphabetically");
b.add_input<decl::Bool>("Reset Children"_ustr)
.description(
"Reset the transforms of every child instance in the output. Only used when Separate "
"Children is enabled");
b.add_output<decl::Geometry>("Instances"_ustr)
.description(
"Instance of the collection or instances of all the children in the collection");
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "transform_space", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
static void node_node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCollectionInfo *data = MEM_new<NodeGeometryCollectionInfo>(__func__);
data->transform_space = GEO_NODE_TRANSFORM_SPACE_ORIGINAL;
node->storage = data;
}
struct InstanceListEntry {
int handle;
char *name;
float4x4 transform;
};
static void node_geo_exec(GeoNodeExecParams params)
{
Collection *collection = params.extract_input<Collection *>("Collection"_ustr);
if (collection == nullptr) {
params.set_default_remaining_outputs();
return;
}
const Object *self_object = params.self_object();
/* Compare by `orig_id` because objects may be copied into separate depsgraphs. */
const bool is_recursive = BKE_collection_has_object_recursive_instanced_orig_id(
collection, const_cast<Object *>(self_object));
if (is_recursive) {
params.error_message_add(NodeWarningType::Error, TIP_("Collection contains current object"));
params.set_default_remaining_outputs();
return;
}
if (!DEG_collection_geometry_is_evaluated(*collection)) {
params.error_message_add(NodeWarningType::Error,
TIP_("Cannot access collections geometry because it's not evaluated "
"yet. This can happen when there is a dependency cycle"));
params.set_default_remaining_outputs();
return;
}
const NodeGeometryCollectionInfo &storage = node_storage(params.node());
const bool use_relative_transform = (storage.transform_space ==
GEO_NODE_TRANSFORM_SPACE_RELATIVE);
std::unique_ptr<bke::Instances> instances = std::make_unique<bke::Instances>();
const bool separate_children = params.extract_input<bool>("Separate Children"_ustr);
if (separate_children) {
const bool reset_children = params.extract_input<bool>("Reset Children"_ustr);
Vector<Collection *> children_collections;
for (CollectionChild &collection_child : collection->children) {
children_collections.append(collection_child.collection);
}
Vector<Object *> children_objects;
for (CollectionObject &collection_object : collection->gobject) {
children_objects.append(collection_object.ob);
}
Vector<InstanceListEntry> entries;
entries.reserve(children_collections.size() + children_objects.size());
for (Collection *child_collection : children_collections) {
float4x4 transform = float4x4::identity();
if (!reset_children) {
transform.location() += float3(child_collection->instance_offset);
if (use_relative_transform) {
transform = self_object->world_to_object() * transform;
}
else {
transform.location() -= float3(collection->instance_offset);
}
}
const int handle = instances->add_reference(*child_collection);
entries.append({handle, &(child_collection->id.name[2]), transform});
}
for (Object *child_object : children_objects) {
const int handle = instances->add_reference(*child_object);
float4x4 transform = float4x4::identity();
if (!reset_children) {
if (use_relative_transform) {
transform = self_object->world_to_object();
}
else {
transform.location() -= float3(collection->instance_offset);
}
transform *= child_object->object_to_world();
}
entries.append({handle, &(child_object->id.name[2]), transform});
}
std::ranges::sort(entries, [](const InstanceListEntry &a, const InstanceListEntry &b) {
return BLI_strcasecmp_natural(a.name, b.name) < 0;
});
instances->resize(entries.size());
MutableSpan<int> handles = instances->reference_handles_for_write();
MutableSpan<float4x4> transforms = instances->transforms_for_write();
for (const int i : entries.index_range()) {
handles[i] = entries[i].handle;
transforms[i] = entries[i].transform;
}
}
else {
float4x4 transform = float4x4::identity();
if (use_relative_transform) {
transform.location() = collection->instance_offset;
transform = self_object->world_to_object() * transform;
}
instances->resize(1);
instances->reference_handles_for_write().first() = instances->add_reference(*collection);
instances->transforms_for_write().first() = transform;
}
GeometrySet geometry = GeometrySet::from_instances(std::move(instances));
geometry.set_name(collection->id.name + 2);
params.set_output("Instances"_ustr, std::move(geometry));
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem rna_node_geometry_collection_info_transform_space_items[] = {
{GEO_NODE_TRANSFORM_SPACE_ORIGINAL,
"ORIGINAL",
0,
"Original",
"Output the geometry relative to the collection offset"},
{GEO_NODE_TRANSFORM_SPACE_RELATIVE,
"RELATIVE",
0,
"Relative",
"Bring the input collection geometry into the modified object, maintaining the relative "
"position between the objects in the scene"},
{0, nullptr, 0, nullptr, nullptr},
};
PropertyRNA *prop = RNA_def_node_enum(
srna,
"transform_space",
"Transform Space",
"The transformation of the instances output. Does not affect the internal geometry",
rna_node_geometry_collection_info_transform_space_items,
NOD_storage_enum_accessors(transform_space),
GEO_NODE_TRANSFORM_SPACE_ORIGINAL);
RNA_def_property_update_runtime(prop, rna_Node_update_relations);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCollectionInfo"_ustr, GEO_NODE_COLLECTION_INFO);
ntype.ui_name = "Collection Info";
ntype.ui_description = "Retrieve geometry instances from a collection";
ntype.enum_name_legacy = "COLLECTION_INFO";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.initfunc = node_node_init;
bke::node_type_storage(
ntype, "NodeGeometryCollectionInfo", node_free_standard_storage, node_copy_standard_storage);
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_collection_info_cc

View File

@@ -0,0 +1,297 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_geo_bundle.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_socket_items_blend.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "NOD_socket_search_link.hh"
#include "NOD_sync_sockets.hh"
#include "BKE_idprop.hh"
#include "BLO_read_write.hh"
#include "UI_interface_layout.hh"
#include "node_geometry_util.hh"
#include "shader/node_shader_util.hh"
namespace blender {
namespace nodes::node_geo_combine_bundle_cc {
NODE_STORAGE_FUNCS(NodeCombineBundle);
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
const bNodeTree *tree = b.tree_or_null();
const bNode *node = b.node_or_null();
b.add_output<decl::Bundle>("Bundle"_ustr).propagate_all().structure_type(StructureType::Single);
if (tree && node) {
FlatBundleTypePtr flat_bundle_type;
if (const std::optional<StringRefNull> type = combine_bundle_node_type(*tree, *node)) {
flat_bundle_type = BundleTypeRegistry::try_find_single_flat(*type);
}
const NodeCombineBundle &storage = node_storage(*node);
for (const int i : IndexRange(storage.items_num)) {
const NodeCombineBundleItem &item = storage.items[i];
const eNodeSocketDatatype socket_type = item.socket_type;
const UString name(item.name);
const UString identifier(CombineBundleItemsAccessor::socket_identifier_for_item(item));
auto &decl = b.add_input(socket_type, name, identifier)
.socket_name_ptr(
&tree->id, *CombineBundleItemsAccessor::item_srna, &item, "name");
if (item.structure_type != NodeSocketInterfaceStructureType::Auto) {
decl.structure_type(StructureType(item.structure_type));
}
else {
decl.structure_type(StructureType::Dynamic);
}
if (flat_bundle_type) {
if (const SocketDeclaration *src_decl = flat_bundle_type->find_decl(name)) {
decl.try_copy_ui_data(*src_decl);
}
}
if (i == 0 && socket_type == SOCK_STRING && name == Bundle::type_item_name.ustr()) {
decl.optional_label();
b.add_separator();
}
}
b.add_input<decl::Extend>(""_ustr, "__extend__"_ustr)
.custom_draw(socket_items::ui::draw_extend_socket_fn<CombineBundleItemsAccessor>());
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
auto *storage = MEM_new<NodeCombineBundle>(__func__);
node->storage = storage;
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeCombineBundle &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_new<NodeCombineBundle>(__func__, dna::shallow_copy(src_storage));
dst_node->storage = dst_storage;
socket_items::copy_array<CombineBundleItemsAccessor>(*src_node, *dst_node);
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<CombineBundleItemsAccessor>(*node);
MEM_delete(static_cast<NodeCombineBundle *>(node->storage));
}
static bool node_insert_link(bke::NodeInsertLinkParams &params)
{
if (params.C && params.link.fromnode == &params.node && params.link.tosock->type == SOCK_BUNDLE)
{
const NodeCombineBundle &storage = node_storage(params.node);
if (storage.items_num == 0) {
SpaceNode *snode = CTX_wm_space_node(params.C);
if (snode && snode->edittree == &params.ntree) {
sync_sockets_combine_bundle(*snode, params.node, nullptr, params.link.tosock);
}
}
return true;
}
return socket_items::try_add_item_via_any_extend_socket<CombineBundleItemsAccessor>(
params.ntree, params.node, params.node, params.link);
}
static void node_layout_ex(ui::Layout &layout, bContext *C, PointerRNA *node_ptr)
{
bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(node_ptr->owner_id);
bNode &node = *static_cast<bNode *>(node_ptr->data);
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
layout.op("node.sockets_sync", IFACE_("Sync"), ICON_FILE_REFRESH);
layout.prop(node_ptr, "define_signature", UI_ITEM_NONE, std::nullopt, ICON_NONE);
if (ui::Layout *panel = layout.panel(C, "bundle_items", false, IFACE_("Bundle Items"))) {
socket_items::ui::draw_items_list_with_operators<CombineBundleItemsAccessor>(
C, panel, ntree, node);
socket_items::ui::draw_active_item_props<CombineBundleItemsAccessor>(
ntree, node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, IFACE_("Type"), ICON_NONE);
panel->prop(item_ptr, "structure_type", UI_ITEM_NONE, IFACE_("Shape"), ICON_NONE);
});
}
}
static void node_operators()
{
socket_items::ops::make_common_operators<CombineBundleItemsAccessor>();
}
static void node_geo_exec(GeoNodeExecParams params)
{
const bNode &node = params.node();
const NodeCombineBundle &storage = node_storage(node);
BundlePtr bundle_ptr = Bundle::create();
Bundle &bundle = bundle_ptr.ensure_mutable_inplace();
for (const int i : IndexRange(storage.items_num)) {
const NodeCombineBundleItem &item = storage.items[i];
const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type);
if (!stype || !stype->geometry_nodes_default_value) {
continue;
}
const StringRef name = item.name;
const std::optional<BundleKey> key = BundleKey::from_str(name);
if (!key) {
continue;
}
bke::SocketValueVariant value = params.extract_input<bke::SocketValueVariant>(
node.input_socket(i).identifier_ustr());
bundle.add(*key, BundleItemSocketValue{stype, std::move(value)});
}
params.set_output("Bundle"_ustr, std::move(bundle_ptr));
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const bNodeSocket &other_socket = params.other_socket();
if (other_socket.in_out == SOCK_OUT) {
if (!CombineBundleItemsAccessor::supports_socket_type(other_socket.typeinfo->type,
params.node_tree().type))
{
return;
}
params.add_item(IFACE_("Item"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("NodeCombineBundle"_ustr);
const auto *item =
socket_items::add_item_with_socket_type_and_name<CombineBundleItemsAccessor>(
params.node_tree, node, params.socket.typeinfo->type, params.socket.name);
params.update_and_connect_available_socket(node, UString(item->name));
});
}
else {
if (other_socket.type != SOCK_BUNDLE) {
return;
}
params.add_item(IFACE_("Bundle"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("NodeCombineBundle"_ustr);
params.connect_available_socket(node, "Bundle"_ustr);
SpaceNode &snode = *CTX_wm_space_node(&params.C);
sync_sockets_combine_bundle(snode, node, nullptr);
});
}
}
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
{
socket_items::blend_write<CombineBundleItemsAccessor>(&writer, node);
}
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
{
socket_items::blend_read_data<CombineBundleItemsAccessor>(&reader, node);
}
static void node_register()
{
static bke::bNodeType ntype;
sh_geo_node_type_base(&ntype, "NodeCombineBundle"_ustr, NODE_COMBINE_BUNDLE);
ntype.ui_name = "Combine Bundle";
ntype.ui_description = "Combine multiple socket values into one.";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.geometry_node_execute = node_geo_exec;
ntype.insert_link = node_insert_link;
ntype.draw_buttons_ex = node_layout_ex;
ntype.gather_link_search_ops = node_gather_link_searches;
ntype.register_operators = node_operators;
ntype.blend_write_storage_content = node_blend_write;
ntype.blend_data_read_storage_content = node_blend_read;
bke::node_type_storage(ntype, "NodeCombineBundle", node_free_storage, node_copy_storage);
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace nodes::node_geo_combine_bundle_cc
namespace nodes {
StructRNA **CombineBundleItemsAccessor::item_srna = &RNA_NodeCombineBundleItem;
void CombineBundleItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
writer->write_string(item.name);
}
void CombineBundleItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
std::string CombineBundleItemsAccessor::validate_name(const StringRef name)
{
std::string result;
if (name.is_empty()) {
return result;
}
const Span<char> forbidden_chars = BundleKey::forbidden_key_chars;
for (const char c : name) {
if (forbidden_chars.contains(c)) {
result += '_';
}
else {
result += c;
}
}
if (!result.empty()) {
/* Disallow leading spaces. */
if (std::isspace(result[0])) {
result[0] = '_';
}
/* Disallow trailing spaces. */
const int last_index = result.size() - 1;
if (std::isspace(result[last_index])) {
result[last_index] = '_';
}
}
BLI_assert(BundleKey::is_valid_key(result));
return result;
}
std::optional<StringRefNull> combine_bundle_node_type(const bNodeTree & /*tree*/,
const bNode &node)
{
BLI_assert(node.is_type("NodeCombineBundle"_ustr));
/* Not using topology cache because this is called while building the node. */
for (const bNodeSocket &socket : node.inputs) {
if (socket.type != SOCK_STRING) {
continue;
}
if (socket.name != Bundle::type_item_name.ustr()) {
continue;
}
return socket.default_value_typed<bNodeSocketValueString>()->value;
}
return std::nullopt;
}
} // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_node.hh"
#include "NOD_geometry.hh"
#include "NOD_common.hh"
#include "node_common.h"
#include "node_geometry_util.hh"
#include "RNA_access.hh"
namespace blender {
namespace nodes {
static void register_node_type_geo_group()
{
static bke::bNodeType ntype;
bke::node_type_base_custom(ntype, "GeometryNodeGroup", "Group", "GROUP", NODE_CLASS_GROUP);
ntype.enum_name_legacy = "GROUP";
ntype.type_legacy = NODE_GROUP;
ntype.poll = geo_node_poll_default;
ntype.poll_instance = node_group_poll_instance;
ntype.insert_link = node_insert_link_default;
ntype.ui_class = node_group_ui_class;
ntype.ui_description_fn = node_group_ui_description;
ntype.rna_ext.srna = RNA_struct_find("GeometryNodeGroup");
BLI_assert(ntype.rna_ext.srna != nullptr);
RNA_struct_blender_type_set(ntype.rna_ext.srna, &ntype);
ntype.minwidth = bke::NodeWidth::GroupMin;
ntype.labelfunc = node_group_label;
ntype.declare = node_group_declare;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(register_node_type_geo_group)
} // namespace nodes
void register_node_type_geo_custom_group(bke::bNodeType *ntype)
{
/* These methods can be overridden but need a default implementation otherwise. */
if (ntype->poll == nullptr) {
ntype->poll = geo_node_poll_default;
}
if (ntype->insert_link == nullptr) {
ntype->insert_link = node_insert_link_default;
}
ntype->declare = nodes::node_group_declare;
}
} // namespace blender

View File

@@ -0,0 +1,301 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_pointcloud_types.h"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_material.hh"
#include "BKE_mesh.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_join_geometries.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
#ifdef WITH_BULLET
# include "RBI_hull_api.h"
#endif
namespace blender::nodes::node_geo_convex_hull_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry"_ustr).description("Points to compute the convex hull of");
b.add_output<decl::Geometry>("Convex Hull"_ustr).propagate_all_geometry();
}
#ifdef WITH_BULLET
static Mesh *hull_from_bullet(const Mesh *mesh, Span<float3> coords)
{
plConvexHull hull = plConvexHullCompute((float (*)[3])coords.data(), coords.size());
const int verts_num = plConvexHullNumVertices(hull);
const int faces_num = verts_num <= 2 ? 0 : plConvexHullNumFaces(hull);
const int loops_num = verts_num <= 2 ? 0 : plConvexHullNumLoops(hull);
/* Half as many edges as loops, because the mesh is manifold. */
const int edges_num = verts_num == 2 ? 1 : verts_num < 2 ? 0 : loops_num / 2;
/* Create Mesh *result with proper capacity. */
Mesh *result;
if (mesh) {
result = BKE_mesh_new_nomain_from_template(mesh, verts_num, edges_num, faces_num, loops_num);
}
else {
result = BKE_mesh_new_nomain(verts_num, edges_num, faces_num, loops_num);
BKE_id_material_eval_ensure_default_slot(&result->id);
}
bke::mesh_smooth_set(*result, false);
/* Copy vertices. */
MutableSpan<float3> dst_positions = result->vert_positions_for_write();
for (const int i : IndexRange(verts_num)) {
float3 dummy_co;
int original_index;
plConvexHullGetVertex(hull, i, dummy_co, &original_index);
if (UNLIKELY(!coords.index_range().contains(original_index))) {
BLI_assert_unreachable();
dst_positions[i] = float3(0);
continue;
}
dst_positions[i] = coords[original_index];
}
/* Copy edges and loops. */
/* NOTE: ConvexHull from Bullet uses a half-edge data structure
* for its mesh. To convert that, each half-edge needs to be converted
* to a loop and edges need to be created from that. */
Array<int> corner_verts(loops_num);
Array<int> corner_edges(loops_num);
uint edge_index = 0;
MutableSpan<int2> edges = result->edges_for_write();
for (const int i : IndexRange(loops_num)) {
int v_from;
int v_to;
plConvexHullGetLoop(hull, i, &v_from, &v_to);
corner_verts[i] = v_from;
/* Add edges for ascending order loops only. */
if (v_from < v_to) {
edges[edge_index] = int2(v_from, v_to);
/* Write edge index into both loops that have it. */
int reverse_index = plConvexHullGetReversedLoopIndex(hull, i);
corner_edges[i] = edge_index;
corner_edges[reverse_index] = edge_index;
edge_index++;
}
}
if (edges_num == 1) {
/* In this case there are no loops. */
edges[0] = int2(0, 1);
edge_index++;
}
BLI_assert(edge_index == edges_num);
/* Copy faces. */
Array<int> loops;
int j = 0;
MutableSpan<int> face_offsets = result->face_offsets_for_write();
MutableSpan<int> mesh_corner_verts = result->corner_verts_for_write();
MutableSpan<int> mesh_corner_edges = result->corner_edges_for_write();
int dst_corner = 0;
for (const int i : IndexRange(faces_num)) {
const int len = plConvexHullGetFaceSize(hull, i);
BLI_assert(len > 2);
/* Get face loop indices. */
loops.reinitialize(len);
plConvexHullGetFaceLoops(hull, i, loops.data());
face_offsets[i] = j;
for (const int k : IndexRange(len)) {
mesh_corner_verts[dst_corner] = corner_verts[loops[k]];
mesh_corner_edges[dst_corner] = corner_edges[loops[k]];
dst_corner++;
}
j += len;
}
plConvexHullDelete(hull);
return result;
}
static Mesh *compute_hull(const GeometrySet &geometry_set)
{
int span_count = 0;
int count = 0;
int total_num = 0;
Span<float3> positions_span;
if (const Mesh *mesh = geometry_set.get_mesh()) {
count++;
if (const VArray positions = *mesh->attributes().lookup<float3>("position")) {
if (positions.is_span()) {
span_count++;
positions_span = positions.get_internal_span();
}
total_num += positions.size();
}
}
if (const PointCloud *points = geometry_set.get_pointcloud()) {
count++;
if (const VArray positions = *points->attributes().lookup<float3>("position")) {
if (positions.is_span()) {
span_count++;
positions_span = positions.get_internal_span();
}
total_num += positions.size();
}
}
if (const Curves *curves_id = geometry_set.get_curves()) {
count++;
span_count++;
const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
positions_span = curves.evaluated_positions();
total_num += positions_span.size();
}
if (count == 0) {
return nullptr;
}
/* If there is only one positions virtual array and it is already contiguous, avoid copying
* all of the positions and instead pass the span directly to the convex hull function. */
if (span_count == 1 && count == 1) {
return hull_from_bullet(geometry_set.get_mesh(), positions_span);
}
Array<float3> positions(total_num);
int offset = 0;
if (const Mesh *mesh = geometry_set.get_mesh()) {
if (const VArray varray = *mesh->attributes().lookup<float3>("position")) {
varray.materialize(positions.as_mutable_span().slice(offset, varray.size()));
offset += varray.size();
}
}
if (const PointCloud *points = geometry_set.get_pointcloud()) {
if (const VArray varray = *points->attributes().lookup<float3>("position")) {
varray.materialize(positions.as_mutable_span().slice(offset, varray.size()));
offset += varray.size();
}
}
if (const Curves *curves_id = geometry_set.get_curves()) {
const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
Span<float3> array = curves.evaluated_positions();
positions.as_mutable_span().slice(offset, array.size()).copy_from(array);
offset += array.size();
}
return hull_from_bullet(geometry_set.get_mesh(), positions);
}
static void convex_hull_grease_pencil(GeometrySet &geometry_set)
{
using namespace blender::bke::greasepencil;
const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil();
Array<Mesh *> mesh_by_layer(grease_pencil.layers().size(), nullptr);
for (const int layer_index : grease_pencil.layers().index_range()) {
const Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &curves = drawing->strokes();
const Span<float3> positions_span = curves.evaluated_positions();
if (positions_span.is_empty()) {
continue;
}
mesh_by_layer[layer_index] = hull_from_bullet(nullptr, positions_span);
}
if (mesh_by_layer.is_empty()) {
return;
}
auto instances = std::make_unique<bke::Instances>(mesh_by_layer.size());
MutableSpan<int> handles = instances->reference_handles_for_write();
instances->transforms_for_write().fill(float4x4::identity());
for (const int i : mesh_by_layer.index_range()) {
Mesh *mesh = mesh_by_layer[i];
if (!mesh) {
/* Add an empty reference so the number of layers and instances match.
* This makes it easy to reconstruct the layers afterwards and keep their attributes.
* Although in this particular case we don't propagate the attributes. */
handles[i] = instances->add_reference(bke::InstanceReference());
continue;
}
GeometrySet temp_set = GeometrySet::from_mesh(mesh);
handles[i] = instances->add_reference(bke::InstanceReference{temp_set});
}
auto &dst_component = geometry_set.get_component_for_write<InstancesComponent>();
GeometrySet new_instances = geometry::join_geometries(
{GeometrySet::from_instances(dst_component.release()),
GeometrySet::from_instances(std::move(instances))},
{});
dst_component.replace(new_instances.get_component_for_write<InstancesComponent>().release());
geometry_set.replace_grease_pencil(nullptr);
}
#endif /* WITH_BULLET */
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"_ustr);
#ifdef WITH_BULLET
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
Mesh *mesh = compute_hull(geometry_set);
if (mesh) {
geometry::debug_randomize_mesh_order(mesh);
}
geometry_set.replace_mesh(mesh);
if (geometry_set.has_grease_pencil()) {
convex_hull_grease_pencil(geometry_set);
}
geometry_set.keep_only({GeometryComponent::Type::Mesh,
GeometryComponent::Type::Instance,
GeometryComponent::Type::Edit});
});
params.set_output("Convex Hull"_ustr, std::move(geometry_set));
#else
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without Bullet"));
params.set_default_remaining_outputs();
#endif /* WITH_BULLET */
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeConvexHull"_ustr, GEO_NODE_CONVEX_HULL);
ntype.ui_name = "Convex Hull";
ntype.ui_description =
"Create a mesh that encloses all points in the input geometry with the smallest number of "
"points";
ntype.enum_name_legacy = "CONVEX_HULL";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_convex_hull_cc

View File

@@ -0,0 +1,133 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_volume.hh"
#include "BKE_volume_grid.hh"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
#include <cstdint>
#ifdef WITH_OPENVDB
# include <openvdb/openvdb.h>
#endif
namespace blender::nodes::node_geo_cube_grid_topology_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.add_output<decl::Bool>("Topology"_ustr)
.structure_type(StructureType::Grid)
.description("Boolean grid defining the topology/active regions");
b.add_input<decl::Vector>("Bounds Min"_ustr)
.default_value(float3(-1.0f))
.description("Minimum boundary of the grid (world space)");
b.add_input<decl::Vector>("Bounds Max"_ustr)
.default_value(float3(1.0f))
.description("Maximum boundary of the grid (world space)");
b.add_input<decl::Int>("Resolution X"_ustr)
.default_value(32)
.min(1)
.description("Number of voxels in the X axis");
b.add_input<decl::Int>("Resolution Y"_ustr)
.default_value(32)
.min(1)
.description("Number of voxels in the Y axis");
b.add_input<decl::Int>("Resolution Z"_ustr)
.default_value(32)
.min(1)
.description("Number of voxels in the Z axis");
PanelDeclarationBuilder &min_panel = b.add_panel("Min"_ustr).default_closed(true);
min_panel.add_input<decl::Int>("Min X"_ustr)
.default_value(0)
.description("Minimum coordinate in X axis (grid index space)");
min_panel.add_input<decl::Int>("Min Y"_ustr)
.default_value(0)
.description("Minimum coordinate in Y axis (grid index space)");
min_panel.add_input<decl::Int>("Min Z"_ustr)
.default_value(0)
.description("Minimum coordinate in Z axis (grid index space)");
}
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
using type_traits = typename bke::VolumeGridTraits<bool>;
using TreeType = typename type_traits::TreeType;
using GridType = openvdb::Grid<TreeType>;
auto openvdb_grid = GridType::create(false /* background */);
const int3 grid_min_inclusive = int3(params.extract_input<int>("Min X"_ustr),
params.extract_input<int>("Min Y"_ustr),
params.extract_input<int>("Min Z"_ustr));
const int3 resolution = int3(params.extract_input<int>("Resolution X"_ustr),
params.extract_input<int>("Resolution Y"_ustr),
params.extract_input<int>("Resolution Z"_ustr));
const float3 bounds_min = params.extract_input<float3>("Bounds Min"_ustr);
const float3 bounds_max = params.extract_input<float3>("Bounds Max"_ustr);
if (resolution.x <= 0 || resolution.y <= 0 || resolution.z <= 0) {
params.error_message_add(NodeWarningType::Warning, TIP_("Resolution must be positive"));
params.set_default_remaining_outputs();
return;
}
if (bounds_min.x >= bounds_max.x || bounds_min.y >= bounds_max.y || bounds_min.z >= bounds_max.z)
{
params.error_message_add(NodeWarningType::Error,
TIP_("Bounding box volume must be greater than 0"));
params.set_default_remaining_outputs();
return;
}
const int3 grid_max_inclusive = grid_min_inclusive + resolution - 1;
const openvdb::math::CoordBBox bbox(
{grid_min_inclusive.x, grid_min_inclusive.y, grid_min_inclusive.z},
{grid_max_inclusive.x, grid_max_inclusive.y, grid_max_inclusive.z});
openvdb_grid->tree().denseFill(bbox, true, /*active=*/true);
const double3 scale_fac = double3(bounds_max - bounds_min) / double3(resolution);
if (!BKE_volume_voxel_size_valid(float3(scale_fac))) {
params.error_message_add(NodeWarningType::Warning,
TIP_("Volume scale is lower than permitted by OpenVDB"));
params.set_default_remaining_outputs();
return;
}
openvdb_grid->transform().postScale(openvdb::math::Vec3d(scale_fac.x, scale_fac.y, scale_fac.z));
const double3 translation = double3(bounds_min) - scale_fac * double3(grid_min_inclusive);
openvdb_grid->transform().postTranslate(
openvdb::math::Vec3d(translation.x, translation.y, translation.z));
bke::VolumeGrid<bool> topology_grid(std::move(openvdb_grid));
params.set_output("Topology"_ustr, bke::GVolumeGrid(std::move(topology_grid)));
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCubeGridTopology"_ustr);
ntype.ui_name = "Cube Grid Topology";
ntype.ui_description =
"Create a boolean grid topology with the given dimensions, for use with the Field to Grid "
"node";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.default_width = bke::NodeWidth::_160;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_cube_grid_topology_cc

View File

@@ -0,0 +1,129 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_task.hh"
#include "BKE_curves.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_endpoint_selection_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Int>("Start Size"_ustr)
.min(0)
.default_value(1)
.structure_type(StructureType::Field)
.description("The amount of points to select from the start of each spline");
b.add_input<decl::Int>("End Size"_ustr)
.min(0)
.default_value(1)
.structure_type(StructureType::Field)
.description("The amount of points to select from the end of each spline");
b.add_output<decl::Bool>("Selection"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("The selection from the start and end of the splines based on the input sizes");
}
class EndpointFieldInput final : public bke::GeometryFieldInput {
Field<int> start_size_;
Field<int> end_size_;
public:
EndpointFieldInput(Field<int> start_size, Field<int> end_size)
: bke::GeometryFieldInput(CPPType::get<bool>(), "Endpoint Selection node"),
start_size_(start_size),
end_size_(end_size)
{
}
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
const IndexMask & /*mask*/) const final
{
if (context.domain() != AttrDomain::Point) {
return {};
}
const bke::CurvesGeometry *curves_ptr = context.curves_or_strokes();
if (!curves_ptr) {
return {};
}
const bke::CurvesGeometry &curves = *curves_ptr;
if (curves.is_empty()) {
return {};
}
const bke::GeometryFieldContext sub_context{context, AttrDomain::Curve};
fn::FieldEvaluator evaluator{sub_context, curves.curves_num()};
evaluator.add(start_size_);
evaluator.add(end_size_);
evaluator.evaluate();
const VArray<int> start_size = evaluator.get_evaluated<int>(0);
const VArray<int> end_size = evaluator.get_evaluated<int>(1);
Array<bool> selection(curves.points_num(), false);
MutableSpan<bool> selection_span = selection.as_mutable_span();
const OffsetIndices points_by_curve = curves.points_by_curve();
devirtualize_varray2(start_size, end_size, [&](const auto start_size, const auto end_size) {
threading::parallel_for(curves.curves_range(), 1024, [&](IndexRange curves_range) {
for (const int i : curves_range) {
const IndexRange points = points_by_curve[i];
const int start = std::max(start_size[i], 0);
const int end = std::max(end_size[i], 0);
selection_span.slice(points.take_front(start)).fill(true);
selection_span.slice(points.take_back(end)).fill(true);
}
});
});
return VArray<bool>::from_container(std::move(selection));
};
void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const final
{
fn(start_size_);
fn(end_size_);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const final
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(start_size_));
hash.add(deep_hash_cache.ensure(end_size_));
}
std::optional<AttrDomain> preferred_domain(const GeometryComponent & /*component*/) const final
{
return AttrDomain::Point;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
Field<int> start_size = params.extract_input<Field<int>>("Start Size"_ustr);
Field<int> end_size = params.extract_input<Field<int>>("End Size"_ustr);
Field<bool> selection_field = Field<bool>::from_input<EndpointFieldInput>(start_size, end_size);
params.set_output("Selection"_ustr, std::move(selection_field));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeCurveEndpointSelection"_ustr, GEO_NODE_CURVE_ENDPOINT_SELECTION);
ntype.ui_name = "Endpoint Selection";
ntype.ui_description = "Provide a selection for an arbitrary number of endpoints in each spline";
ntype.enum_name_legacy = "CURVE_ENDPOINT_SELECTION";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_endpoint_selection_cc

View File

@@ -0,0 +1,394 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_array_utils.hh"
#include "BLI_delaunay_2d.hh"
#include "BLI_math_vector_types.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_mesh.hh"
#include "BLI_task.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_join_geometries.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_fill_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurveFill)
static const EnumPropertyItem mode_items[] = {
{GEO_NODE_CURVE_FILL_MODE_TRIANGULATED, "TRIANGLES", 0, N_("Triangles"), ""},
{GEO_NODE_CURVE_FILL_MODE_NGONS, "NGONS", 0, N_("N-gons"), ""},
{0, nullptr, 0, nullptr, nullptr},
};
/* See #CDT_output_type in BLI_delaunay_2d.hh for winding rule details. */
static const EnumPropertyItem fill_rule_items[] = {
{GEO_NODE_CURVE_FILL_RULE_EVEN_ODD,
"EVEN_ODD",
0,
N_("Even-Odd"),
N_("Alternate inside/outside based on crossing count")},
{GEO_NODE_CURVE_FILL_RULE_NON_ZERO,
"NON_ZERO",
0,
N_("Non-Zero"),
N_("Overlapping curves with the same winding direction are filled as a union")},
{0, nullptr, 0, nullptr, nullptr},
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Curve"_ustr)
.supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil})
.description(
"Curves to fill. All curves are treated as cyclic and projected to the XY plane");
b.add_input<decl::Int>("Group ID"_ustr)
.evaluated_geometry_field()
.hide_value()
.description(
"An index used to group curves together. Filling is done separately for each group");
b.add_input<decl::Menu>("Mode"_ustr)
.static_items(mode_items)
.default_value(GEO_NODE_CURVE_FILL_MODE_TRIANGULATED)
.optional_label();
b.add_input<decl::Menu>("Fill Rule"_ustr)
.static_items(fill_rule_items)
.default_value(GEO_NODE_CURVE_FILL_RULE_EVEN_ODD)
.optional_label()
.description("Rule used to determine which regions are inside or outside");
b.add_output<decl::Geometry>("Mesh"_ustr).propagate_all_geometry();
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
/* Still used for forward compatibility. */
node->storage = MEM_new<NodeGeometryCurveFill>(__func__);
}
static void fill_curve_vert_indices(const OffsetIndices<int> offsets,
MutableSpan<Vector<int>> faces)
{
threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
faces[i].resize(offsets[i].size());
array_utils::fill_index_range<int>(faces[i], offsets[i].start());
}
});
}
static meshintersect::CDT_result<double> do_cdt(const bke::CurvesGeometry &curves,
const CDT_output_type output_type)
{
const OffsetIndices points_by_curve = curves.evaluated_points_by_curve();
const Span<float3> positions = curves.evaluated_positions();
Array<double2> positions_2d(positions.size());
threading::parallel_for(positions.index_range(), 2048, [&](const IndexRange range) {
for (const int i : range) {
positions_2d[i] = double2(positions[i].x, positions[i].y);
}
});
Array<Vector<int>> faces(curves.curves_num());
fill_curve_vert_indices(points_by_curve, faces);
meshintersect::CDT_input<double> input;
input.need_ids = false;
input.vert = std::move(positions_2d);
input.face = std::move(faces);
return delaunay_2d_calc(input, output_type);
}
static meshintersect::CDT_result<double> do_cdt_with_mask(const bke::CurvesGeometry &curves,
const CDT_output_type output_type,
const IndexMask &mask)
{
const OffsetIndices points_by_curve = curves.evaluated_points_by_curve();
const Span<float3> positions = curves.evaluated_positions();
Array<int> offsets_data(mask.size() + 1);
const OffsetIndices points_by_curve_masked = offset_indices::gather_selected_offsets(
points_by_curve, mask, offsets_data);
Array<double2> positions_2d(points_by_curve_masked.total_size());
mask.foreach_index(
[&](const int src_curve, const int dst_curve) {
const IndexRange src_points = points_by_curve[src_curve];
const IndexRange dst_points = points_by_curve_masked[dst_curve];
for (const int i : src_points.index_range()) {
const int src = src_points[i];
const int dst = dst_points[i];
positions_2d[dst] = double2(positions[src].x, positions[src].y);
}
},
exec_mode::grain_size(1024));
Array<Vector<int>> faces(points_by_curve_masked.size());
fill_curve_vert_indices(points_by_curve_masked, faces);
meshintersect::CDT_input<double> input;
input.need_ids = false;
input.vert = std::move(positions_2d);
input.face = std::move(faces);
return delaunay_2d_calc(input, output_type);
}
static Array<meshintersect::CDT_result<double>> do_group_aware_cdt(
const bke::CurvesGeometry &curves,
const CDT_output_type output_type,
const Field<int> &group_index_field)
{
const bke::GeometryFieldContext field_context{curves, AttrDomain::Curve};
fn::FieldEvaluator data_evaluator{field_context, curves.curves_num()};
data_evaluator.add(group_index_field);
data_evaluator.evaluate();
const VArray<int> curve_group_ids = data_evaluator.get_evaluated<int>(0);
if (curve_group_ids.is_single()) {
return {do_cdt(curves, output_type)};
}
VectorSet<int> group_indexing;
IndexMaskMemory mask_memory;
const Vector<IndexMask> group_masks = IndexMask::from_group_ids(
curve_group_ids, mask_memory, group_indexing);
const int groups_num = group_masks.size();
Array<meshintersect::CDT_result<double>> cdt_results(groups_num);
/* The grain size should be larger as each group gets smaller. */
const int domain_size = curve_group_ids.size();
const int avg_group_size = domain_size / groups_num;
const int grain_size = std::max(8192 / avg_group_size, 1);
threading::parallel_for(IndexRange(groups_num), grain_size, [&](const IndexRange range) {
for (const int group_index : range) {
const IndexMask &mask = group_masks[group_index];
cdt_results[group_index] = do_cdt_with_mask(curves, output_type, mask);
}
});
return cdt_results;
}
/* Converts multiple CDT results into a single Mesh. */
static Mesh *cdts_to_mesh(const Span<meshintersect::CDT_result<double>> results)
{
/* Converting a single CDT result to a Mesh would be simple because the indices could be re-used.
* However, in the general case here we need to combine several CDT results into a single Mesh,
* which requires us to map the original indices to a new set of indices.
* In order to allow for parallelization when appropriate, this implementation starts by
* determining (for each domain) what range of indices in the final mesh data will be used for
* each CDT result. The index ranges are represented as offsets, which are referred to as "group
* offsets" to distinguish them from the other types of offsets we need to work with here.
* Since it's likely that most invocations will only have a single CDT result, it's important
* that case is made as optimal as feasible. */
Array<int> vert_groups_data(results.size() + 1);
Array<int> edge_groups_data(results.size() + 1);
Array<int> face_groups_data(results.size() + 1);
Array<int> loop_groups_data(results.size() + 1);
threading::parallel_for(results.index_range(), 1024, [&](const IndexRange results_range) {
for (const int i_result : results_range) {
const meshintersect::CDT_result<double> &result = results[i_result];
vert_groups_data[i_result] = result.vert.size();
edge_groups_data[i_result] = result.edge.size();
face_groups_data[i_result] = result.face.size();
int loop_len = 0;
for (const Vector<int> &face : result.face) {
loop_len += face.size();
}
loop_groups_data[i_result] = loop_len;
}
});
const OffsetIndices vert_groups = offset_indices::accumulate_counts_to_offsets(vert_groups_data);
const OffsetIndices edge_groups = offset_indices::accumulate_counts_to_offsets(edge_groups_data);
const OffsetIndices face_groups = offset_indices::accumulate_counts_to_offsets(face_groups_data);
const OffsetIndices loop_groups = offset_indices::accumulate_counts_to_offsets(loop_groups_data);
Mesh *mesh = BKE_mesh_new_nomain(vert_groups.total_size(),
edge_groups.total_size(),
face_groups.total_size(),
loop_groups.total_size());
MutableSpan<float3> all_positions = mesh->vert_positions_for_write();
MutableSpan<int2> all_edges = mesh->edges_for_write();
MutableSpan<int> all_face_offsets = mesh->face_offsets_for_write();
MutableSpan<int> all_corner_verts = mesh->corner_verts_for_write();
threading::parallel_for(results.index_range(), 1024, [&](const IndexRange results_range) {
for (const int i_result : results_range) {
const meshintersect::CDT_result<double> &result = results[i_result];
const IndexRange verts_range = vert_groups[i_result];
const IndexRange edges_range = edge_groups[i_result];
const IndexRange faces_range = face_groups[i_result];
const IndexRange loops_range = loop_groups[i_result];
MutableSpan<float3> positions = all_positions.slice(verts_range);
for (const int i : result.vert.index_range()) {
positions[i] = float3(float(result.vert[i].x), float(result.vert[i].y), 0.0f);
}
MutableSpan<int2> edges = all_edges.slice(edges_range);
for (const int i : result.edge.index_range()) {
edges[i] = int2(result.edge[i].first + verts_range.start(),
result.edge[i].second + verts_range.start());
}
MutableSpan<int> face_offsets = all_face_offsets.slice(faces_range);
MutableSpan<int> corner_verts = all_corner_verts.slice(loops_range);
int i_face_corner = 0;
for (const int i_face : result.face.index_range()) {
face_offsets[i_face] = i_face_corner + loops_range.start();
for (const int i_corner : result.face[i_face].index_range()) {
corner_verts[i_face_corner] = result.face[i_face][i_corner] + verts_range.start();
i_face_corner++;
}
}
}
});
/* The delaunay triangulation doesn't seem to return all of the necessary all_edges, even in
* triangulation mode. */
bke::mesh_calc_edges(*mesh, true, false);
bke::mesh_smooth_set(*mesh, false);
mesh->tag_overlapping_none();
return mesh;
}
static void curve_fill_calculate(GeometrySet &geometry_set,
const GeometryNodeCurveFillMode mode,
const GeometryNodeCurveFillRule fill_rule,
const Field<int> &group_index)
{
/* Determine CDT output type based on mode and fill rule. */
CDT_output_type output_type;
if (mode == GEO_NODE_CURVE_FILL_MODE_NGONS) {
output_type = CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES;
switch (fill_rule) {
case GEO_NODE_CURVE_FILL_RULE_NON_ZERO: {
output_type = CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES_NONZERO;
break;
}
case GEO_NODE_CURVE_FILL_RULE_EVEN_ODD: {
/* Default, already set. */
break;
}
}
}
else {
/* Triangulated mode. */
output_type = CDT_INSIDE_WITH_HOLES;
switch (fill_rule) {
case GEO_NODE_CURVE_FILL_RULE_NON_ZERO: {
output_type = CDT_INSIDE_WITH_HOLES_NONZERO;
break;
}
case GEO_NODE_CURVE_FILL_RULE_EVEN_ODD: {
/* Default, already set. */
break;
}
}
}
if (geometry_set.has_curves()) {
const Curves &curves_id = *geometry_set.get_curves();
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
if (curves.curves_num() > 0) {
const Array<meshintersect::CDT_result<double>> results = do_group_aware_cdt(
curves, output_type, group_index);
Mesh *mesh = cdts_to_mesh(results);
geometry_set.replace_mesh(mesh);
}
geometry_set.replace_curves(nullptr);
}
if (geometry_set.has_grease_pencil()) {
using namespace blender::bke::greasepencil;
const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil();
Vector<Mesh *> mesh_by_layer(grease_pencil.layers().size(), nullptr);
for (const int layer_index : grease_pencil.layers().index_range()) {
const Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &src_curves = drawing->strokes();
if (src_curves.is_empty()) {
continue;
}
const Array<meshintersect::CDT_result<double>> results = do_group_aware_cdt(
src_curves, output_type, group_index);
mesh_by_layer[layer_index] = cdts_to_mesh(results);
}
if (!mesh_by_layer.is_empty()) {
auto instances = std::make_unique<bke::Instances>(mesh_by_layer.size());
MutableSpan<int> handles = instances->reference_handles_for_write();
instances->transforms_for_write().fill(float4x4::identity());
for (const int i : mesh_by_layer.index_range()) {
Mesh *mesh = mesh_by_layer[i];
if (!mesh) {
/* Add an empty reference so the number of layers and instances match.
* This makes it easy to reconstruct the layers afterwards and keep their attributes.
* Although in this particular case we don't propagate the attributes. */
handles[i] = instances->add_reference(bke::InstanceReference());
continue;
}
GeometrySet temp_set = GeometrySet::from_mesh(mesh);
handles[i] = instances->add_reference(bke::InstanceReference{temp_set});
}
auto &dst_component = geometry_set.get_component_for_write<InstancesComponent>();
GeometrySet new_instances = geometry::join_geometries(
{GeometrySet::from_instances(dst_component.release()),
GeometrySet::from_instances(std::move(instances))},
{});
dst_component.replace(new_instances.get_component_for_write<InstancesComponent>().release());
}
geometry_set.replace_grease_pencil(nullptr);
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"_ustr);
Field<int> group_index = params.extract_input<Field<int>>("Group ID"_ustr);
const GeometryNodeCurveFillMode mode = params.extract_input<GeometryNodeCurveFillMode>(
"Mode"_ustr);
const auto fill_rule = params.extract_input<GeometryNodeCurveFillRule>("Fill Rule"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry) {
curve_fill_calculate(geometry, mode, fill_rule, group_index);
});
params.set_output("Mesh"_ustr, std::move(geometry_set));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeFillCurve"_ustr, GEO_NODE_FILL_CURVE);
ntype.ui_name = "Fill Curve";
ntype.ui_description =
"Generate a mesh on the XY plane with faces on the inside of input curves";
ntype.enum_name_legacy = "FILL_CURVE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.initfunc = node_init;
bke::node_type_storage(
ntype, "NodeGeometryCurveFill", node_free_standard_storage, node_copy_standard_storage);
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_fill_cc

View File

@@ -0,0 +1,191 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_grease_pencil_types.h"
#include "BKE_grease_pencil.hh"
#include "GEO_fillet_curves.hh"
#include "GEO_foreach_geometry.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_fillet_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurveFillet)
static EnumPropertyItem mode_items[] = {
{GEO_NODE_CURVE_FILLET_BEZIER,
"BEZIER",
0,
N_("Bézier"),
N_("Align Bézier handles to create circular arcs at each control point")},
{GEO_NODE_CURVE_FILLET_POLY,
"POLY",
0,
N_("Poly"),
N_("Add control points along a circular arc (handle type is vector if Bézier Spline)")},
{0, nullptr, 0, nullptr, nullptr},
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Geometry>("Curve"_ustr)
.supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil})
.description("Curves to generate rounded corners on");
b.add_output<decl::Geometry>("Curve"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Float>("Radius"_ustr)
.min(0.0f)
.max(FLT_MAX)
.subtype(PropertySubType::PROP_DISTANCE)
.default_value(0.25f)
.evaluated_geometry_field();
b.add_input<decl::Bool>("Limit Radius"_ustr)
.description("Limit the maximum value of the radius in order to avoid overlapping fillets");
b.add_input<decl::Menu>("Mode"_ustr)
.static_items(mode_items)
.optional_label()
.description("How to choose number of vertices on fillet");
b.add_input<decl::Int>("Count"_ustr)
.default_value(1)
.min(1)
.max(1000)
.evaluated_geometry_field()
.usage_by_single_menu(GEO_NODE_CURVE_FILLET_POLY);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
/* Still used for forward compatibility. */
node->storage = MEM_new<NodeGeometryCurveFillet>(__func__);
}
static bke::CurvesGeometry fillet_curve(const bke::CurvesGeometry &src_curves,
const GeometryNodeCurveFilletMode mode,
const fn::FieldContext &field_context,
const std::optional<Field<int>> &count_field,
const Field<float> &radius_field,
const bool limit_radius,
const AttributeFilter &attribute_filter)
{
fn::FieldEvaluator evaluator{field_context, src_curves.points_num()};
evaluator.add(radius_field);
switch (mode) {
case GEO_NODE_CURVE_FILLET_BEZIER: {
evaluator.evaluate();
return geometry::fillet_curves_bezier(src_curves,
src_curves.curves_range(),
evaluator.get_evaluated<float>(0),
limit_radius,
attribute_filter);
}
case GEO_NODE_CURVE_FILLET_POLY: {
evaluator.add(*count_field);
evaluator.evaluate();
return geometry::fillet_curves_poly(src_curves,
src_curves.curves_range(),
evaluator.get_evaluated<float>(0),
evaluator.get_evaluated<int>(1),
limit_radius,
attribute_filter);
}
}
return bke::CurvesGeometry();
}
static void fillet_grease_pencil(GreasePencil &grease_pencil,
const GeometryNodeCurveFilletMode mode,
const std::optional<Field<int>> &count_field,
const Field<float> &radius_field,
const bool limit_radius,
const AttributeFilter &attribute_filter)
{
using namespace blender::bke::greasepencil;
for (const int layer_index : grease_pencil.layers().index_range()) {
Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &src_curves = drawing->strokes();
if (src_curves.is_empty()) {
continue;
}
const bke::GreasePencilLayerFieldContext field_context(
grease_pencil, AttrDomain::Curve, layer_index);
bke::CurvesGeometry dst_curves = fillet_curve(src_curves,
mode,
field_context,
count_field,
radius_field,
limit_radius,
attribute_filter);
drawing->strokes_for_write() = std::move(dst_curves);
drawing->tag_topology_changed();
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"_ustr);
const GeometryNodeCurveFilletMode mode = params.extract_input<GeometryNodeCurveFilletMode>(
"Mode"_ustr);
Field<float> radius_field = params.extract_input<Field<float>>("Radius"_ustr);
const bool limit_radius = params.extract_input<bool>("Limit Radius"_ustr);
std::optional<Field<int>> count_field;
if (mode == GEO_NODE_CURVE_FILLET_POLY) {
count_field.emplace(params.extract_input<Field<int>>("Count"_ustr));
}
const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Curve"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
if (geometry_set.has_curves()) {
const Curves &curves_id = *geometry_set.get_curves();
const bke::CurvesGeometry &src_curves = curves_id.geometry.wrap();
const bke::CurvesFieldContext field_context{curves_id, AttrDomain::Point};
bke::CurvesGeometry dst_curves = fillet_curve(src_curves,
mode,
field_context,
count_field,
radius_field,
limit_radius,
attribute_filter);
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
bke::curves_copy_parameters(curves_id, *dst_curves_id);
geometry_set.replace_curves(dst_curves_id);
}
if (geometry_set.has_grease_pencil()) {
GreasePencil &grease_pencil = *geometry_set.get_grease_pencil_for_write();
fillet_grease_pencil(
grease_pencil, mode, count_field, radius_field, limit_radius, attribute_filter);
}
});
params.set_output("Curve"_ustr, std::move(geometry_set));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeFilletCurve"_ustr, GEO_NODE_FILLET_CURVE);
ntype.ui_name = "Fillet Curve";
ntype.ui_description = "Round corners by generating circular arcs on each control point";
ntype.enum_name_legacy = "FILLET_CURVE";
ntype.nclass = NODE_CLASS_GEOMETRY;
bke::node_type_storage(
ntype, "NodeGeometryCurveFillet", node_free_standard_storage, node_copy_standard_storage);
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_fillet_cc

View File

@@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_handle_type_selection_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurveSelectHandles)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Bool>("Selection"_ustr).structure_type(StructureType::Field);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
layout.prop(ptr, "handle_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurveSelectHandles *data = MEM_new<NodeGeometryCurveSelectHandles>(__func__);
data->handle_type = GEO_NODE_CURVE_HANDLE_AUTO;
data->mode = GEO_NODE_CURVE_HANDLE_LEFT | GEO_NODE_CURVE_HANDLE_RIGHT;
node->storage = data;
}
static HandleType handle_type_from_input_type(const GeometryNodeCurveHandleType type)
{
switch (type) {
case GEO_NODE_CURVE_HANDLE_AUTO:
return BEZIER_HANDLE_AUTO;
case GEO_NODE_CURVE_HANDLE_ALIGN:
return BEZIER_HANDLE_ALIGN;
case GEO_NODE_CURVE_HANDLE_FREE:
return BEZIER_HANDLE_FREE;
case GEO_NODE_CURVE_HANDLE_VECTOR:
return BEZIER_HANDLE_VECTOR;
}
BLI_assert_unreachable();
return BEZIER_HANDLE_AUTO;
}
static void select_by_handle_type(const bke::CurvesGeometry &curves,
const HandleType type,
const GeometryNodeCurveHandleMode mode,
const MutableSpan<bool> r_selection)
{
const OffsetIndices points_by_curve = curves.points_by_curve();
VArray<int8_t> curve_types = curves.curve_types();
VArray<int8_t> left = curves.handle_types_left();
VArray<int8_t> right = curves.handle_types_right();
for (const int i_curve : curves.curves_range()) {
const IndexRange points = points_by_curve[i_curve];
if (curve_types[i_curve] != CURVE_TYPE_BEZIER) {
r_selection.slice(points).fill(false);
}
else {
for (const int i_point : points) {
r_selection[i_point] = (mode & GEO_NODE_CURVE_HANDLE_LEFT && left[i_point] == type) ||
(mode & GEO_NODE_CURVE_HANDLE_RIGHT && right[i_point] == type);
}
}
}
}
class HandleTypeFieldInput final : public bke::CurvesFieldInput {
HandleType type_;
GeometryNodeCurveHandleMode mode_;
public:
HandleTypeFieldInput(HandleType type, GeometryNodeCurveHandleMode mode)
: bke::CurvesFieldInput(CPPType::get<bool>(), "Handle Type Selection node"),
type_(type),
mode_(mode)
{
}
GVArray get_varray_for_context(const bke::CurvesGeometry &curves,
const AttrDomain domain,
const IndexMask &mask) const final
{
if (domain != AttrDomain::Point) {
return {};
}
Array<bool> selection(mask.min_array_size());
select_by_handle_type(curves, type_, mode_, selection);
return VArray<bool>::from_container(std::move(selection));
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep & /*deep_hash_cache*/) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(type_);
hash.add(mode_);
}
std::optional<AttrDomain> preferred_domain(const bke::CurvesGeometry & /*curves*/) const final
{
return AttrDomain::Point;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryCurveSelectHandles &storage = node_storage(params.node());
const HandleType handle_type = handle_type_from_input_type(
(GeometryNodeCurveHandleType)storage.handle_type);
const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)storage.mode;
Field<bool> selection_field = Field<bool>::from_input<HandleTypeFieldInput>(handle_type, mode);
params.set_output("Selection"_ustr, std::move(selection_field));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeCurveHandleTypeSelection"_ustr, GEO_NODE_CURVE_HANDLE_TYPE_SELECTION);
ntype.ui_name = "Handle Type Selection";
ntype.ui_description = "Provide a selection based on the handle types of Bézier control points";
ntype.enum_name_legacy = "CURVE_HANDLE_TYPE_SELECTION";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.initfunc = node_init;
ntype.default_width = bke::NodeWidth::_160;
bke::node_type_storage(ntype,
"NodeGeometryCurveSelectHandles",
node_free_standard_storage,
node_copy_standard_storage);
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_handle_type_selection_cc

View File

@@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_length_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Curve"_ustr)
.supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil})
.description("Curve to compute the length of");
b.add_output<decl::Float>("Length"_ustr);
}
static float curves_total_length(const bke::CurvesGeometry &curves)
{
const VArray<bool> cyclic = curves.cyclic();
curves.ensure_evaluated_lengths();
float total_length = 0.0f;
for (const int i : curves.curves_range()) {
total_length += curves.evaluated_length_total_for_curve(i, cyclic[i]);
}
return total_length;
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"_ustr);
float length = 0.0f;
if (geometry_set.has_curves()) {
const Curves &curves_id = *geometry_set.get_curves();
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
length += curves_total_length(curves);
}
else if (geometry_set.has_grease_pencil()) {
using namespace bke::greasepencil;
const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil();
for (const int layer_index : grease_pencil.layers().index_range()) {
const Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &curves = drawing->strokes();
length += curves_total_length(curves);
}
}
else {
params.set_default_remaining_outputs();
return;
}
params.set_output("Length"_ustr, length);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCurveLength"_ustr, GEO_NODE_CURVE_LENGTH);
ntype.ui_name = "Curve Length";
ntype.ui_description = "Retrieve the length of all splines added together";
ntype.enum_name_legacy = "CURVE_LENGTH";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_length_cc

View File

@@ -0,0 +1,392 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BKE_curves.hh"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_primitive_arc_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurvePrimitiveArc)
static void node_declare(NodeDeclarationBuilder &b)
{
auto enable_points = [](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS;
};
auto enable_radius = [](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS;
};
b.add_input<decl::Int>("Resolution"_ustr)
.default_value(16)
.min(2)
.max(256)
.subtype(PROP_UNSIGNED)
.description("The number of points on the arc");
auto &start = b.add_input<decl::Vector>("Start"_ustr)
.default_value({-1.0f, 0.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("Position of the first control point")
.make_available(enable_points);
auto &middle = b.add_input<decl::Vector>("Middle"_ustr)
.default_value({0.0f, 2.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("Position of the middle control point")
.make_available(enable_points);
auto &end = b.add_input<decl::Vector>("End"_ustr)
.default_value({1.0f, 0.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("Position of the last control point")
.make_available(enable_points);
auto &radius = b.add_input<decl::Float>("Radius"_ustr)
.default_value(1.0f)
.min(0.0f)
.subtype(PROP_DISTANCE)
.description("Distance of the points from the origin")
.make_available(enable_radius);
auto &start_angle = b.add_input<decl::Float>("Start Angle"_ustr)
.default_value(0.0f)
.subtype(PROP_ANGLE)
.description("Starting angle of the arc")
.make_available(enable_radius);
auto &sweep_angle = b.add_input<decl::Float>("Sweep Angle"_ustr)
.default_value(1.75f * M_PI)
.min(-2 * M_PI)
.max(2 * M_PI)
.subtype(PROP_ANGLE)
.description("Length of the arc")
.make_available(enable_radius);
auto &offset_angle = b.add_input<decl::Float>("Offset Angle"_ustr)
.default_value(0.0f)
.subtype(PROP_ANGLE)
.description("Offset angle of the arc")
.make_available(enable_points);
b.add_input<decl::Bool>("Connect Center"_ustr)
.default_value(false)
.description("Connect the arc at the center");
b.add_input<decl::Bool>("Invert Arc"_ustr)
.default_value(false)
.description("Invert and draw opposite arc");
b.add_output<decl::Geometry>("Curve"_ustr);
auto &center_out = b.add_output<decl::Vector>("Center"_ustr)
.description("The center of the circle described by the three points")
.make_available(enable_points);
auto &normal_out = b.add_output<decl::Vector>("Normal"_ustr)
.description(
"The normal direction of the plane described by the three points, "
"pointing towards the positive Z axis")
.make_available(enable_points);
auto &radius_out = b.add_output<decl::Float>("Radius"_ustr)
.description("The radius of the circle described by the three points")
.make_available(enable_points);
const bNode *node = b.node_or_null();
if (node != nullptr) {
const NodeGeometryCurvePrimitiveArc &storage = node_storage(*node);
const GeometryNodeCurvePrimitiveArcMode mode = GeometryNodeCurvePrimitiveArcMode(storage.mode);
const bool radius_mode = (mode == GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS);
const bool points_mode = (mode == GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS);
start.available(points_mode);
middle.available(points_mode);
end.available(points_mode);
radius.available(radius_mode);
start_angle.available(radius_mode);
sweep_angle.available(radius_mode);
offset_angle.available(points_mode);
center_out.available(points_mode);
normal_out.available(points_mode);
radius_out.available(points_mode);
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurvePrimitiveArc *data = MEM_new<NodeGeometryCurvePrimitiveArc>(__func__);
data->mode = GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS;
node->storage = data;
}
static float3 rotate_vector_around_axis(const float3 vector, const float3 axis, const float angle)
{
float3 result = vector;
float mat[3][3];
axis_angle_to_mat3(mat, axis, angle);
mul_m3_v3(mat, result);
return result;
}
static bool colinear_f3_f3_f3(const float3 p1, const float3 p2, const float3 p3)
{
const float3 a = math::normalize(p2 - p1);
const float3 b = math::normalize(p3 - p1);
return ELEM(a, b, b * -1.0f);
}
static Curves *create_arc_curve_from_points(const int resolution,
const float3 a,
const float3 b,
const float3 c,
float angle_offset,
const bool connect_center,
const bool invert_arc,
float3 &r_center,
float3 &r_normal,
float &r_radius)
{
const int size = connect_center ? resolution + 1 : resolution;
Curves *curves_id = bke::curves_new_nomain_single(size, CURVE_TYPE_POLY);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
const int stepcount = resolution - 1;
const int centerpoint = resolution;
MutableSpan<float3> positions = curves.positions_for_write();
const bool is_colinear = colinear_f3_f3_f3(a, b, c);
float3 center;
float3 normal;
float radius;
const float3 mid_ac = math::midpoint(a, c);
normal_tri_v3(normal, a, c, b);
if (is_colinear || a == c || a == b || b == c || resolution == 2) {
/* If colinear, generate a point line between points. */
float3 p1, p2;
/* Find the two points that are furthest away from each other. */
const float ab = math::distance_squared(a, b);
const float ac = math::distance_squared(a, c);
const float bc = math::distance_squared(b, c);
if (ab > ac && ab > bc) {
p1 = a;
p2 = b;
}
else if (bc > ab && bc > ac) {
p1 = b;
p2 = c;
}
else {
p1 = a;
p2 = c;
}
const float step = 1.0f / stepcount;
for (const int i : IndexRange(resolution)) {
const float factor = step * i;
positions[i] = math::interpolate(p1, p2, factor);
}
center = mid_ac;
radius = 0.0f;
}
else {
/* Midpoints of `A->B` and `B->C`. */
const float3 mid_ab = math::midpoint(a, b);
const float3 mid_bc = math::midpoint(c, b);
/* Normalized vectors of `A->B` and `B->C`. */
const float3 nba = math::normalize(b - a);
const float3 ncb = math::normalize(c - b);
/* Normal of plane of main 2 segments A->B and `B->C`. */
const float3 nabc = math::normalize(math::cross(nba, ncb));
/* Determine center point from the intersection of 3 planes. */
float plane_1[4], plane_2[4], plane_3[4];
plane_from_point_normal_v3(plane_1, mid_ab, nabc);
plane_from_point_normal_v3(plane_2, mid_ab, nba);
plane_from_point_normal_v3(plane_3, mid_bc, ncb);
/* If the 3 planes do not intersect at one point, just return empty geometry. */
if (!isect_plane_plane_plane_v3(plane_1, plane_2, plane_3, center)) {
r_center = mid_ac;
r_normal = normal;
r_radius = 0.0f;
return nullptr;
}
/* Radial vectors. */
const float3 rad_a = math::normalize(a - center);
const float3 rad_b = math::normalize(b - center);
const float3 rad_c = math::normalize(c - center);
/* Calculate angles. */
radius = math::distance(center, b);
float angle_ab = angle_signed_on_axis_v3v3_v3(rad_a, rad_b, normal) + 2.0f * M_PI;
float angle_ac = angle_signed_on_axis_v3v3_v3(rad_a, rad_c, normal) + 2.0f * M_PI;
float angle = (angle_ac > angle_ab) ? angle_ac : angle_ab;
angle -= 2.0f * M_PI;
if (invert_arc) {
angle = -(2.0f * M_PI - angle);
}
/* Create arc. */
const float step = angle / stepcount;
for (const int i : IndexRange(resolution)) {
const float factor = step * i + angle_offset;
float3 out = rotate_vector_around_axis(rad_a, -normal, factor);
positions[i] = out * radius + center;
}
}
if (connect_center) {
curves.cyclic_for_write().first() = true;
positions[centerpoint] = center;
}
/* Ensure normal is relative to Z-up. */
if (math::dot(float3(0, 0, 1), normal) < 0) {
normal = -normal;
}
r_center = center;
r_radius = radius;
r_normal = normal;
return curves_id;
}
static Curves *create_arc_curve_from_radius(const int resolution,
const float radius,
const float start_angle,
const float sweep_angle,
const bool connect_center,
const bool invert_arc)
{
const int size = connect_center ? resolution + 1 : resolution;
Curves *curves_id = bke::curves_new_nomain_single(size, CURVE_TYPE_POLY);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
const int stepcount = resolution - 1;
const int centerpoint = resolution;
MutableSpan<float3> positions = curves.positions_for_write();
const float sweep = (invert_arc) ? -(2.0f * M_PI - sweep_angle) : sweep_angle;
const float theta_step = sweep / float(stepcount);
for (const int i : IndexRange(resolution)) {
const float theta = theta_step * i + start_angle;
const float x = radius * cos(theta);
const float y = radius * sin(theta);
positions[i] = float3(x, y, 0.0f);
}
if (connect_center) {
curves.cyclic_for_write().first() = true;
positions[centerpoint] = float3(0.0f, 0.0f, 0.0f);
}
return curves_id;
}
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryCurvePrimitiveArc &storage = node_storage(params.node());
const GeometryNodeCurvePrimitiveArcMode mode = GeometryNodeCurvePrimitiveArcMode(storage.mode);
switch (mode) {
case GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS: {
float3 center, normal;
float radius;
Curves *curves = create_arc_curve_from_points(
std::max(params.extract_input<int>("Resolution"_ustr), 2),
params.extract_input<float3>("Start"_ustr),
params.extract_input<float3>("Middle"_ustr),
params.extract_input<float3>("End"_ustr),
params.extract_input<float>("Offset Angle"_ustr),
params.extract_input<bool>("Connect Center"_ustr),
params.extract_input<bool>("Invert Arc"_ustr),
center,
normal,
radius);
params.set_output("Curve"_ustr, GeometrySet::from_curves(curves));
params.set_output("Center"_ustr, center);
params.set_output("Normal"_ustr, normal);
params.set_output("Radius"_ustr, radius);
break;
}
case GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS: {
Curves *curves = create_arc_curve_from_radius(
std::max(params.extract_input<int>("Resolution"_ustr), 2),
params.extract_input<float>("Radius"_ustr),
params.extract_input<float>("Start Angle"_ustr),
params.extract_input<float>("Sweep Angle"_ustr),
params.extract_input<bool>("Connect Center"_ustr),
params.extract_input<bool>("Invert Arc"_ustr));
params.set_output("Curve"_ustr, GeometrySet::from_curves(curves));
break;
}
}
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem mode_items[] = {
{GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS,
"POINTS",
ICON_NONE,
"Points",
"Define arc by 3 points on circle. Arc is calculated between start and end points"},
{GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS,
"RADIUS",
ICON_NONE,
"Radius",
"Define radius with a float"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"mode",
"Mode",
"Method used to determine radius and placement",
mode_items,
NOD_storage_enum_accessors(mode),
GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCurveArc"_ustr, GEO_NODE_CURVE_PRIMITIVE_ARC);
ntype.ui_name = "Arc";
ntype.ui_description = "Generate a poly spline arc";
ntype.enum_name_legacy = "CURVE_PRIMITIVE_ARC";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.initfunc = node_init;
bke::node_type_storage(ntype,
"NodeGeometryCurvePrimitiveArc",
node_free_standard_storage,
node_copy_standard_storage);
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_primitive_arc_cc

View File

@@ -0,0 +1,167 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_primitive_bezier_segment_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurvePrimitiveBezierSegment)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Int>("Resolution"_ustr)
.default_value(16)
.min(1)
.max(256)
.subtype(PROP_UNSIGNED)
.description("The number of evaluated points on the curve");
b.add_input<decl::Vector>("Start"_ustr)
.default_value({-1.0f, 0.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("Position of the start control point of the curve");
b.add_input<decl::Vector>("Start Handle"_ustr)
.default_value({-0.5f, 0.5f, 0.0f})
.subtype(PROP_TRANSLATION)
.description(
"Position of the start handle used to define the shape of the curve. In Offset mode, "
"relative to Start point");
b.add_input<decl::Vector>("End Handle"_ustr)
.subtype(PROP_TRANSLATION)
.description(
"Position of the end handle used to define the shape of the curve. In Offset mode, "
"relative to End point");
b.add_input<decl::Vector>("End"_ustr)
.default_value({1.0f, 0.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("Position of the end control point of the curve");
b.add_output<decl::Geometry>("Curve"_ustr);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurvePrimitiveBezierSegment *data = MEM_new<NodeGeometryCurvePrimitiveBezierSegment>(
__func__);
data->mode = GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT_POSITION;
node->storage = data;
}
static Curves *create_bezier_segment_curve(const float3 start,
const float3 start_handle_right,
const float3 end,
const float3 end_handle_left,
const int resolution,
const GeometryNodeCurvePrimitiveBezierSegmentMode mode)
{
Curves *curves_id = bke::curves_new_nomain_single(2, CURVE_TYPE_BEZIER);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
curves.resolution_for_write().fill(resolution);
MutableSpan<float3> positions = curves.positions_for_write();
curves.handle_types_left_for_write().fill(BEZIER_HANDLE_ALIGN);
curves.handle_types_right_for_write().fill(BEZIER_HANDLE_ALIGN);
positions.first() = start;
positions.last() = end;
MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
if (mode == GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT_POSITION) {
handles_left.first() = 2.0f * start - start_handle_right;
handles_right.first() = start_handle_right;
handles_left.last() = end_handle_left;
handles_right.last() = 2.0f * end - end_handle_left;
}
else {
handles_left.first() = start - start_handle_right;
handles_right.first() = start + start_handle_right;
handles_left.last() = end + end_handle_left;
handles_right.last() = end - end_handle_left;
}
return curves_id;
}
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryCurvePrimitiveBezierSegment &storage = node_storage(params.node());
const GeometryNodeCurvePrimitiveBezierSegmentMode mode =
GeometryNodeCurvePrimitiveBezierSegmentMode(storage.mode);
Curves *curves = create_bezier_segment_curve(
params.extract_input<float3>("Start"_ustr),
params.extract_input<float3>("Start Handle"_ustr),
params.extract_input<float3>("End"_ustr),
params.extract_input<float3>("End Handle"_ustr),
std::max(params.extract_input<int>("Resolution"_ustr), 1),
mode);
params.set_output("Curve"_ustr, GeometrySet::from_curves(curves));
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem mode_items[] = {
{GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT_POSITION,
"POSITION",
ICON_NONE,
"Position",
"The start and end handles are fixed positions"},
{GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT_OFFSET,
"OFFSET",
ICON_NONE,
"Offset",
"The start and end handles are offsets from the spline's control points"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"mode",
"Mode",
"Method used to determine control handles",
mode_items,
NOD_storage_enum_accessors(mode),
GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT_POSITION);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype,
"GeometryNodeCurvePrimitiveBezierSegment"_ustr,
GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT);
ntype.ui_name = "Bézier Segment";
ntype.ui_description = "Generate a 2D Bézier spline from the given control points and handles";
ntype.enum_name_legacy = "CURVE_PRIMITIVE_BEZIER_SEGMENT";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.initfunc = node_init;
bke::node_type_storage(ntype,
"NodeGeometryCurvePrimitiveBezierSegment",
node_free_standard_storage,
node_copy_standard_storage);
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_primitive_bezier_segment_cc

View File

@@ -0,0 +1,254 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "BLI_math_geom.h"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_primitive_circle_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurvePrimitiveCircle)
static void node_declare(NodeDeclarationBuilder &b)
{
auto endable_points = [](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS;
};
auto enable_radius = [](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS;
};
b.add_input<decl::Int>("Resolution"_ustr)
.default_value(32)
.min(3)
.max(512)
.description("Number of points on the circle");
auto &start = b.add_input<decl::Vector>("Point 1"_ustr)
.default_value({-1.0f, 0.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description(
"One of the three points on the circle. The point order determines the "
"circle's direction")
.make_available(endable_points);
auto &middle = b.add_input<decl::Vector>("Point 2"_ustr)
.default_value({0.0f, 1.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description(
"One of the three points on the circle. The point order determines the "
"circle's direction")
.make_available(endable_points);
auto &end = b.add_input<decl::Vector>("Point 3"_ustr)
.default_value({1.0f, 0.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description(
"One of the three points on the circle. The point order determines the "
"circle's direction")
.make_available(endable_points);
auto &radius = b.add_input<decl::Float>("Radius"_ustr)
.default_value(1.0f)
.min(0.0f)
.subtype(PROP_DISTANCE)
.description("Distance of the points from the origin")
.make_available(enable_radius);
b.add_output<decl::Geometry>("Curve"_ustr);
auto &center = b.add_output<decl::Vector>("Center"_ustr).make_available(endable_points);
const bNode *node = b.node_or_null();
if (node != nullptr) {
const NodeGeometryCurvePrimitiveCircle &storage = node_storage(*node);
const GeometryNodeCurvePrimitiveCircleMode mode = GeometryNodeCurvePrimitiveCircleMode(
storage.mode);
start.available(mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS);
middle.available(mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS);
end.available(mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS);
center.available(mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS);
radius.available(mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS);
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurvePrimitiveCircle *data = MEM_new<NodeGeometryCurvePrimitiveCircle>(__func__);
data->mode = GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS;
node->storage = data;
}
static bool colinear_f3_f3_f3(const float3 p1, const float3 p2, const float3 p3)
{
const float3 a = math::normalize(p2 - p1);
const float3 b = math::normalize(p3 - p1);
return ELEM(a, b, b * -1.0f);
}
static Curves *create_point_circle_curve(
const float3 p1, const float3 p2, const float3 p3, const int resolution, float3 &r_center)
{
if (colinear_f3_f3_f3(p1, p2, p3)) {
r_center = float3(0);
return nullptr;
}
float3 center;
/* Midpoints of `P1->P2` and `P2->P3`. */
const float3 q1 = math::interpolate(p1, p2, 0.5f);
const float3 q2 = math::interpolate(p2, p3, 0.5f);
/* Normal Vectors of `P1->P2` and `P2->P3` */
const float3 v1 = math::normalize(p2 - p1);
const float3 v2 = math::normalize(p3 - p2);
/* Normal of plane of main 2 segments P1->P2 and `P2->P3`. */
const float3 v3 = math::normalize(math::cross(v1, v2));
/* Normal of plane of first perpendicular bisector and `P1->P2`. */
const float3 v4 = math::normalize(math::cross(v3, v1));
/* Determine Center-point from the intersection of 3 planes. */
float plane_1[4], plane_2[4], plane_3[4];
plane_from_point_normal_v3(plane_1, q1, v3);
plane_from_point_normal_v3(plane_2, q1, v1);
plane_from_point_normal_v3(plane_3, q2, v2);
/* If the 3 planes do not intersect at one point, just return empty geometry. */
if (!isect_plane_plane_plane_v3(plane_1, plane_2, plane_3, center)) {
r_center = float3(0);
return nullptr;
}
Curves *curves_id = bke::curves_new_nomain_single(resolution, CURVE_TYPE_POLY);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
curves.cyclic_for_write().first() = true;
MutableSpan<float3> positions = curves.positions_for_write();
/* Get the radius from the center-point to p1. */
const float r = math::distance(p1, center);
const float theta_step = ((2 * M_PI) / float(resolution));
for (const int i : IndexRange(resolution)) {
/* Formula for a circle around a point and 2 unit vectors perpendicular
* to each other and the axis of the circle from:
* https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
*/
const float theta = theta_step * i;
positions[i] = center + r * sin(theta) * v1 + r * cos(theta) * v4;
}
r_center = center;
return curves_id;
}
static Curves *create_radius_circle_curve(const int resolution, const float radius)
{
Curves *curves_id = bke::curves_new_nomain_single(resolution, CURVE_TYPE_POLY);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
curves.cyclic_for_write().first() = true;
MutableSpan<float3> positions = curves.positions_for_write();
const float theta_step = (2.0f * M_PI) / float(resolution);
for (int i : IndexRange(resolution)) {
const float theta = theta_step * i;
const float x = radius * cos(theta);
const float y = radius * sin(theta);
positions[i] = float3(x, y, 0.0f);
}
return curves_id;
}
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryCurvePrimitiveCircle &storage = node_storage(params.node());
const GeometryNodeCurvePrimitiveCircleMode mode = GeometryNodeCurvePrimitiveCircleMode(
storage.mode);
Curves *curves = nullptr;
if (mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS) {
float3 center_point;
curves = create_point_circle_curve(params.extract_input<float3>("Point 1"_ustr),
params.extract_input<float3>("Point 2"_ustr),
params.extract_input<float3>("Point 3"_ustr),
std::max(params.extract_input<int>("Resolution"_ustr), 3),
center_point);
params.set_output("Center"_ustr, center_point);
}
else if (mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS) {
curves = create_radius_circle_curve(std::max(params.extract_input<int>("Resolution"_ustr), 3),
params.extract_input<float>("Radius"_ustr));
}
if (curves) {
params.set_output("Curve"_ustr, GeometrySet::from_curves(curves));
}
else {
params.set_default_remaining_outputs();
}
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem mode_items[] = {
{GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS,
"POINTS",
ICON_NONE,
"Points",
"Define the radius and location with three points"},
{GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS,
"RADIUS",
ICON_NONE,
"Radius",
"Define the radius with a float"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"mode",
"Mode",
"Method used to determine radius and placement",
mode_items,
NOD_storage_enum_accessors(mode),
GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeCurvePrimitiveCircle"_ustr, GEO_NODE_CURVE_PRIMITIVE_CIRCLE);
ntype.ui_name = "Curve Circle";
ntype.ui_description = "Generate a poly spline circle";
ntype.enum_name_legacy = "CURVE_PRIMITIVE_CIRCLE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.initfunc = node_init;
bke::node_type_storage(ntype,
"NodeGeometryCurvePrimitiveCircle",
node_free_standard_storage,
node_copy_standard_storage);
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_primitive_circle_cc

View File

@@ -0,0 +1,161 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_primitive_line_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurvePrimitiveLine)
static void node_declare(NodeDeclarationBuilder &b)
{
auto enable_direction = [](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_DIRECTION;
};
b.add_input<decl::Vector>("Start"_ustr)
.subtype(PROP_TRANSLATION)
.description("Position of the first control point");
auto &end = b.add_input<decl::Vector>("End"_ustr)
.default_value({0.0f, 0.0f, 1.0f})
.subtype(PROP_TRANSLATION)
.description("Position of the second control point")
.make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_POINTS;
});
auto &direction =
b.add_input<decl::Vector>("Direction"_ustr)
.default_value({0.0f, 0.0f, 1.0f})
.description("Direction the line is going in. The length of this vector does not matter")
.make_available(enable_direction);
auto &length = b.add_input<decl::Float>("Length"_ustr)
.default_value(1.0f)
.subtype(PROP_DISTANCE)
.description("Distance between the two points")
.make_available(enable_direction);
b.add_output<decl::Geometry>("Curve"_ustr);
const bNode *node = b.node_or_null();
if (node != nullptr) {
const NodeGeometryCurvePrimitiveLine &storage = node_storage(*node);
const GeometryNodeCurvePrimitiveLineMode mode = GeometryNodeCurvePrimitiveLineMode(
storage.mode);
end.available(mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_POINTS);
direction.available(mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_DIRECTION);
length.available(mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_DIRECTION);
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurvePrimitiveLine *data = MEM_new<NodeGeometryCurvePrimitiveLine>(__func__);
data->mode = GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_POINTS;
node->storage = data;
}
static Curves *create_point_line_curve(const float3 start, const float3 end)
{
Curves *curves_id = bke::curves_new_nomain_single(2, CURVE_TYPE_POLY);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
curves.positions_for_write().first() = start;
curves.positions_for_write().last() = end;
return curves_id;
}
static Curves *create_direction_line_curve(const float3 start,
const float3 direction,
const float length)
{
Curves *curves_id = bke::curves_new_nomain_single(2, CURVE_TYPE_POLY);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
curves.positions_for_write().first() = start;
curves.positions_for_write().last() = math::normalize(direction) * length + start;
return curves_id;
}
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryCurvePrimitiveLine &storage = node_storage(params.node());
const GeometryNodeCurvePrimitiveLineMode mode = GeometryNodeCurvePrimitiveLineMode(storage.mode);
Curves *curves = nullptr;
if (mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_POINTS) {
curves = create_point_line_curve(params.extract_input<float3>("Start"_ustr),
params.extract_input<float3>("End"_ustr));
}
else if (mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_DIRECTION) {
curves = create_direction_line_curve(params.extract_input<float3>("Start"_ustr),
params.extract_input<float3>("Direction"_ustr),
params.extract_input<float>("Length"_ustr));
}
params.set_output("Curve"_ustr, GeometrySet::from_curves(curves));
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem mode_items[] = {
{GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_POINTS,
"POINTS",
ICON_NONE,
"Points",
"Define the start and end points of the line"},
{GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_DIRECTION,
"DIRECTION",
ICON_NONE,
"Direction",
"Define a line with a start point, direction and length"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"mode",
"Mode",
"Method used to determine radius and placement",
mode_items,
NOD_storage_enum_accessors(mode),
GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_POINTS);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCurvePrimitiveLine"_ustr, GEO_NODE_CURVE_PRIMITIVE_LINE);
ntype.ui_name = "Curve Line";
ntype.ui_description = "Generate a poly spline line with two points";
ntype.enum_name_legacy = "CURVE_PRIMITIVE_LINE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.initfunc = node_init;
bke::node_type_storage(ntype,
"NodeGeometryCurvePrimitiveLine",
node_free_standard_storage,
node_copy_standard_storage);
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_primitive_line_cc

View File

@@ -0,0 +1,80 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_primitive_quadratic_bezier_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Int>("Resolution"_ustr)
.default_value(16)
.min(3)
.max(256)
.subtype(PROP_UNSIGNED)
.description("The number of edges on the curve");
b.add_input<decl::Vector>("Start"_ustr)
.default_value({-1.0f, 0.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("Position of the first control point");
b.add_input<decl::Vector>("Middle"_ustr)
.default_value({0.0f, 2.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("Position of the middle control point");
b.add_input<decl::Vector>("End"_ustr)
.default_value({1.0f, 0.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("Position of the last control point");
b.add_output<decl::Geometry>("Curve"_ustr);
}
static Curves *create_quadratic_bezier_curve(const float3 p1,
const float3 p2,
const float3 p3,
const int resolution)
{
Curves *curves_id = bke::curves_new_nomain_single(resolution + 1, CURVE_TYPE_POLY);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
MutableSpan<float3> positions = curves.positions_for_write();
const float step = 1.0f / resolution;
for (const int i : IndexRange(resolution + 1)) {
const float factor = step * i;
const float3 q1 = math::interpolate(p1, p2, factor);
const float3 q2 = math::interpolate(p2, p3, factor);
positions[i] = math::interpolate(q1, q2, factor);
}
return curves_id;
}
static void node_geo_exec(GeoNodeExecParams params)
{
Curves *curves = create_quadratic_bezier_curve(
params.extract_input<float3>("Start"_ustr),
params.extract_input<float3>("Middle"_ustr),
params.extract_input<float3>("End"_ustr),
std::max(params.extract_input<int>("Resolution"_ustr), 3));
params.set_output("Curve"_ustr, GeometrySet::from_curves(curves));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeCurveQuadraticBezier"_ustr, GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER);
ntype.ui_name = "Quadratic Bézier";
ntype.ui_description =
"Generate a poly spline in a parabola shape with control points positions";
ntype.enum_name_legacy = "CURVE_PRIMITIVE_QUADRATIC_BEZIER";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_primitive_quadratic_bezier_cc

View File

@@ -0,0 +1,337 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_primitive_quadrilateral_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurvePrimitiveQuad)
static void node_declare(NodeDeclarationBuilder &b)
{
auto &width = b.add_input<decl::Float>("Width"_ustr)
.default_value(2.0f)
.min(0.0f)
.subtype(PROP_DISTANCE)
.description("The X axis size of the shape")
.available(false);
auto &height = b.add_input<decl::Float>("Height"_ustr)
.default_value(2.0f)
.min(0.0f)
.subtype(PROP_DISTANCE)
.description("The Y axis size of the shape")
.available(false);
auto &bottom = b.add_input<decl::Float>("Bottom Width"_ustr)
.default_value(4.0f)
.min(0.0f)
.subtype(PROP_DISTANCE)
.description("The X axis size of the shape")
.available(false);
auto &top = b.add_input<decl::Float>("Top Width"_ustr)
.default_value(2.0f)
.min(0.0f)
.subtype(PROP_DISTANCE)
.description("The X axis size of the shape")
.available(false);
auto &offset =
b.add_input<decl::Float>("Offset"_ustr)
.default_value(1.0f)
.subtype(PROP_DISTANCE)
.description(
"For Parallelogram, the relative X difference between the top and bottom edges. For "
"Trapezoid, the amount to move the top edge in the positive X axis")
.available(false);
auto &bottom_height = b.add_input<decl::Float>("Bottom Height"_ustr)
.default_value(3.0f)
.min(0.0f)
.subtype(PROP_DISTANCE)
.description("The distance between the bottom point and the X axis")
.available(false);
auto &top_height = b.add_input<decl::Float>("Top Height"_ustr)
.default_value(1.0f)
.subtype(PROP_DISTANCE)
.description("The distance between the top point and the X axis")
.available(false);
auto &p1 = b.add_input<decl::Vector>("Point 1"_ustr)
.default_value({-1.0f, -1.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("The exact location of the point to use")
.available(false);
auto &p2 = b.add_input<decl::Vector>("Point 2"_ustr)
.default_value({1.0f, -1.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("The exact location of the point to use")
.available(false);
auto &p3 = b.add_input<decl::Vector>("Point 3"_ustr)
.default_value({1.0f, 1.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("The exact location of the point to use")
.available(false);
auto &p4 = b.add_input<decl::Vector>("Point 4"_ustr)
.default_value({-1.0f, 1.0f, 0.0f})
.subtype(PROP_TRANSLATION)
.description("The exact location of the point to use")
.available(false);
b.add_output<decl::Geometry>("Curve"_ustr);
const bNode *node = b.node_or_null();
if (node != nullptr) {
const NodeGeometryCurvePrimitiveQuad &storage = node_storage(*node);
switch (GeometryNodeCurvePrimitiveQuadMode(storage.mode)) {
case GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE:
width.available(true);
height.available(true);
break;
case GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_PARALLELOGRAM:
width.available(true);
height.available(true);
offset.available(true);
break;
case GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_TRAPEZOID:
bottom.available(true);
top.available(true);
offset.available(true);
height.available(true);
break;
case GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_KITE:
width.available(true);
bottom_height.available(true);
top_height.available(true);
break;
case GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_POINTS:
p1.available(true);
p2.available(true);
p3.available(true);
p4.available(true);
break;
}
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurvePrimitiveQuad *data = MEM_new<NodeGeometryCurvePrimitiveQuad>(__func__);
data->mode = GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE;
node->storage = data;
}
class SocketSearchOp {
public:
UString socket_name;
GeometryNodeCurvePrimitiveQuadMode quad_mode;
void operator()(LinkSearchOpParams &params)
{
bNode &node = params.add_node("GeometryNodeCurvePrimitiveQuadrilateral"_ustr);
node_storage(node).mode = quad_mode;
params.update_and_connect_available_socket(node, socket_name);
}
};
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const NodeDeclaration &declaration = *params.node_type().static_declaration;
if (params.in_out() == SOCK_OUT) {
search_link_ops_for_declarations(params, declaration.outputs);
}
else if (params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_FLOAT)) {
params.add_item(IFACE_("Width"),
SocketSearchOp{"Width"_ustr, GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE});
params.add_item(IFACE_("Height"),
SocketSearchOp{"Height"_ustr, GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE});
params.add_item(
IFACE_("Bottom Width"),
SocketSearchOp{"Bottom Width"_ustr, GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_TRAPEZOID});
params.add_item(
IFACE_("Top Width"),
SocketSearchOp{"Top Width"_ustr, GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_TRAPEZOID});
params.add_item(
IFACE_("Offset"),
SocketSearchOp{"Offset"_ustr, GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_PARALLELOGRAM});
params.add_item(IFACE_("Point 1"),
SocketSearchOp{"Point 1"_ustr, GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_POINTS});
}
}
static void create_rectangle_curve(MutableSpan<float3> positions,
const float height,
const float width)
{
positions[0] = float3(width / 2.0f, height / 2.0f, 0.0f);
positions[1] = float3(-width / 2.0f, height / 2.0f, 0.0f);
positions[2] = float3(-width / 2.0f, -height / 2.0f, 0.0f);
positions[3] = float3(width / 2.0f, -height / 2.0f, 0.0f);
}
static void create_points_curve(MutableSpan<float3> positions,
const float3 &p1,
const float3 &p2,
const float3 &p3,
const float3 &p4)
{
positions[0] = p1;
positions[1] = p2;
positions[2] = p3;
positions[3] = p4;
}
static void create_parallelogram_curve(MutableSpan<float3> positions,
const float height,
const float width,
const float offset)
{
positions[0] = float3(width / 2.0f + offset / 2.0f, height / 2.0f, 0.0f);
positions[1] = float3(-width / 2.0f + offset / 2.0f, height / 2.0f, 0.0f);
positions[2] = float3(-width / 2.0f - offset / 2.0f, -height / 2.0f, 0.0f);
positions[3] = float3(width / 2.0f - offset / 2.0f, -height / 2.0f, 0.0f);
}
static void create_trapezoid_curve(MutableSpan<float3> positions,
const float bottom,
const float top,
const float offset,
const float height)
{
positions[0] = float3(top / 2.0f + offset, height / 2.0f, 0.0f);
positions[1] = float3(-top / 2.0f + offset, height / 2.0f, 0.0f);
positions[2] = float3(-bottom / 2.0f, -height / 2.0f, 0.0f);
positions[3] = float3(bottom / 2.0f, -height / 2.0f, 0.0f);
}
static void create_kite_curve(MutableSpan<float3> positions,
const float width,
const float bottom_height,
const float top_height)
{
positions[0] = float3(0, -bottom_height, 0);
positions[1] = float3(width / 2, 0, 0);
positions[2] = float3(0, top_height, 0);
positions[3] = float3(-width / 2.0f, 0, 0);
}
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryCurvePrimitiveQuad &storage = node_storage(params.node());
const GeometryNodeCurvePrimitiveQuadMode mode = GeometryNodeCurvePrimitiveQuadMode(storage.mode);
Curves *curves_id = bke::curves_new_nomain_single(4, CURVE_TYPE_POLY);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
curves.cyclic_for_write().first() = true;
MutableSpan<float3> positions = curves.positions_for_write();
switch (mode) {
case GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE:
create_rectangle_curve(positions,
std::max(params.extract_input<float>("Height"_ustr), 0.0f),
std::max(params.extract_input<float>("Width"_ustr), 0.0f));
break;
case GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_PARALLELOGRAM:
create_parallelogram_curve(positions,
std::max(params.extract_input<float>("Height"_ustr), 0.0f),
std::max(params.extract_input<float>("Width"_ustr), 0.0f),
params.extract_input<float>("Offset"_ustr));
break;
case GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_TRAPEZOID:
create_trapezoid_curve(positions,
std::max(params.extract_input<float>("Bottom Width"_ustr), 0.0f),
std::max(params.extract_input<float>("Top Width"_ustr), 0.0f),
params.extract_input<float>("Offset"_ustr),
std::max(params.extract_input<float>("Height"_ustr), 0.0f));
break;
case GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_KITE:
create_kite_curve(positions,
std::max(params.extract_input<float>("Width"_ustr), 0.0f),
std::max(params.extract_input<float>("Bottom Height"_ustr), 0.0f),
params.extract_input<float>("Top Height"_ustr));
break;
case GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_POINTS:
create_points_curve(positions,
params.extract_input<float3>("Point 1"_ustr),
params.extract_input<float3>("Point 2"_ustr),
params.extract_input<float3>("Point 3"_ustr),
params.extract_input<float3>("Point 4"_ustr));
break;
default:
params.set_default_remaining_outputs();
return;
}
params.set_output("Curve"_ustr, GeometrySet::from_curves(curves_id));
}
static void node_rna(StructRNA *srna)
{
static EnumPropertyItem mode_items[] = {
{GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE,
"RECTANGLE",
0,
"Rectangle",
"Create a rectangle"},
{GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_PARALLELOGRAM,
"PARALLELOGRAM",
0,
"Parallelogram",
"Create a parallelogram"},
{GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_TRAPEZOID,
"TRAPEZOID",
0,
"Trapezoid",
"Create a trapezoid"},
{GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_KITE, "KITE", 0, "Kite", "Create a Kite / Dart"},
{GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_POINTS,
"POINTS",
0,
"Points",
"Create a quadrilateral from four points"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"mode",
"Mode",
"",
mode_items,
NOD_storage_enum_accessors(mode),
GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype,
"GeometryNodeCurvePrimitiveQuadrilateral"_ustr,
GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL);
ntype.ui_name = "Quadrilateral";
ntype.ui_description = "Generate a polygon with four points";
ntype.enum_name_legacy = "CURVE_PRIMITIVE_QUADRILATERAL";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
ntype.initfunc = node_init;
bke::node_type_storage(ntype,
"NodeGeometryCurvePrimitiveQuad",
node_free_standard_storage,
node_copy_standard_storage);
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_primitive_quadrilateral_cc

View File

@@ -0,0 +1,103 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_primitive_spiral_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Int>("Resolution"_ustr)
.default_value(32)
.min(1)
.max(1024)
.subtype(PROP_UNSIGNED)
.description("Number of points in one rotation of the spiral");
b.add_input<decl::Float>("Rotations"_ustr)
.default_value(2.0f)
.min(0.0f)
.description("Number of times the spiral makes a full rotation")
.translation_context(BLT_I18NCONTEXT_ID_NODETREE);
b.add_input<decl::Float>("Start Radius"_ustr)
.default_value(1.0f)
.subtype(PROP_DISTANCE)
.description("Horizontal Distance from the Z axis at the start of the spiral");
b.add_input<decl::Float>("End Radius"_ustr)
.default_value(2.0f)
.subtype(PROP_DISTANCE)
.description("Horizontal Distance from the Z axis at the end of the spiral");
b.add_input<decl::Float>("Height"_ustr)
.default_value(2.0f)
.subtype(PROP_DISTANCE)
.description("The height perpendicular to the base of the spiral");
b.add_input<decl::Bool>("Reverse"_ustr)
.description("Switch the direction from clockwise to counterclockwise");
b.add_output<decl::Geometry>("Curve"_ustr);
}
static Curves *create_spiral_curve(const float rotations,
const int resolution,
const float start_radius,
const float end_radius,
const float height,
const bool direction)
{
const int totalpoints = std::max(int(resolution * rotations), 1);
const float delta_radius = (end_radius - start_radius) / float(totalpoints);
const float delta_height = height / float(totalpoints);
const float delta_theta = (M_PI * 2 * rotations) / float(totalpoints) *
(direction ? 1.0f : -1.0f);
Curves *curves_id = bke::curves_new_nomain_single(totalpoints + 1, CURVE_TYPE_POLY);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
MutableSpan<float3> positions = curves.positions_for_write();
for (const int i : IndexRange(totalpoints + 1)) {
const float theta = i * delta_theta;
const float radius = start_radius + i * delta_radius;
const float x = radius * cos(theta);
const float y = radius * sin(theta);
const float z = delta_height * i;
positions[i] = {x, y, z};
}
return curves_id;
}
static void node_geo_exec(GeoNodeExecParams params)
{
const float rotations = std::max(params.extract_input<float>("Rotations"_ustr), 0.0f);
if (rotations == 0.0f) {
params.set_default_remaining_outputs();
return;
}
Curves *curves = create_spiral_curve(rotations,
std::max(params.extract_input<int>("Resolution"_ustr), 1),
params.extract_input<float>("Start Radius"_ustr),
params.extract_input<float>("End Radius"_ustr),
params.extract_input<float>("Height"_ustr),
params.extract_input<bool>("Reverse"_ustr));
params.set_output("Curve"_ustr, GeometrySet::from_curves(curves));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCurveSpiral"_ustr, GEO_NODE_CURVE_PRIMITIVE_SPIRAL);
ntype.ui_name = "Spiral";
ntype.ui_description = "Generate a poly spline in a spiral shape";
ntype.enum_name_legacy = "CURVE_PRIMITIVE_SPIRAL";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_primitive_spiral_cc

View File

@@ -0,0 +1,106 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_primitive_star_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Int>("Points"_ustr)
.default_value(8)
.min(3)
.max(256)
.subtype(PROP_UNSIGNED)
.description("Number of points on each of the circles");
b.add_input<decl::Float>("Inner Radius"_ustr)
.default_value(1.0f)
.min(0.0f)
.subtype(PROP_DISTANCE)
.description("Radius of the inner circle; can be larger than outer radius");
b.add_input<decl::Float>("Outer Radius"_ustr)
.default_value(2.0f)
.min(0.0f)
.subtype(PROP_DISTANCE)
.description("Radius of the outer circle; can be smaller than inner radius");
b.add_input<decl::Float>("Twist"_ustr)
.subtype(PROP_ANGLE)
.description("The counterclockwise rotation of the inner set of points");
b.add_output<decl::Geometry>("Curve"_ustr);
b.add_output<decl::Bool>("Outer Points"_ustr)
.anonymous_attribute_output()
.description("An attribute field with a selection of the outer points");
}
static Curves *create_star_curve(const float inner_radius,
const float outer_radius,
const float twist,
const int points)
{
Curves *curves_id = bke::curves_new_nomain_single(points * 2, CURVE_TYPE_POLY);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
curves.cyclic_for_write().first() = true;
MutableSpan<float3> positions = curves.positions_for_write();
const float theta_step = (2.0f * M_PI) / float(points);
for (const int i : IndexRange(points)) {
const float x = outer_radius * cos(theta_step * i);
const float y = outer_radius * sin(theta_step * i);
positions[i * 2] = {x, y, 0.0f};
const float inner_x = inner_radius * cos(theta_step * i + theta_step * 0.5f + twist);
const float inner_y = inner_radius * sin(theta_step * i + theta_step * 0.5f + twist);
positions[i * 2 + 1] = {inner_x, inner_y, 0.0f};
}
return curves_id;
}
static void create_selection_output(CurveComponent &component, const StringRef &r_attribute)
{
SpanAttributeWriter<bool> selection =
component.attributes_for_write()->lookup_or_add_for_write_only_span<bool>(r_attribute,
AttrDomain::Point);
for (int i : selection.span.index_range()) {
selection.span[i] = i % 2 == 0;
}
selection.finish();
}
static void node_geo_exec(GeoNodeExecParams params)
{
Curves *curves = create_star_curve(
std::max(params.extract_input<float>("Inner Radius"_ustr), 0.0f),
std::max(params.extract_input<float>("Outer Radius"_ustr), 0.0f),
params.extract_input<float>("Twist"_ustr),
std::max(params.extract_input<int>("Points"_ustr), 3));
GeometrySet output = GeometrySet::from_curves(curves);
if (std::optional<std::string> outer_points_id =
params.get_output_anonymous_attribute_id_if_needed("Outer Points"_ustr))
{
create_selection_output(output.get_component_for_write<CurveComponent>(), *outer_points_id);
}
params.set_output("Curve"_ustr, std::move(output));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCurveStar"_ustr, GEO_NODE_CURVE_PRIMITIVE_STAR);
ntype.ui_name = "Star";
ntype.ui_description =
"Generate a poly spline in a star pattern by connecting alternating points of two circles";
ntype.enum_name_legacy = "CURVE_PRIMITIVE_STAR";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_primitive_star_cc

View File

@@ -0,0 +1,226 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GEO_foreach_geometry.hh"
#include "GEO_resample_curves.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_rna_define.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_resample_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurveResample)
static EnumPropertyItem mode_items[] = {
{GEO_NODE_CURVE_RESAMPLE_EVALUATED,
"EVALUATED",
0,
N_("Evaluated"),
N_("Output the input spline's evaluated points, based on the resolution attribute for NURBS "
"and Bézier splines. Poly splines are unchanged")},
{GEO_NODE_CURVE_RESAMPLE_COUNT,
"COUNT",
0,
N_("Count"),
N_("Sample the specified number of points along each spline")},
{GEO_NODE_CURVE_RESAMPLE_LENGTH,
"LENGTH",
0,
N_("Length"),
N_("Calculate the number of samples by splitting each spline into segments with the "
"specified "
"length")},
{0, nullptr, 0, nullptr, nullptr},
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Geometry>("Curve"_ustr)
.supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil})
.description("Curves to resample");
b.add_output<decl::Geometry>("Curve"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.evaluated_geometry_field()
.hide_value();
b.add_input<decl::Menu>("Mode"_ustr)
.static_items(mode_items)
.optional_label()
.description("How to specify the amount of samples");
b.add_input<decl::Int>("Count"_ustr)
.default_value(10)
.min(1)
.max(100000)
.evaluated_geometry_field()
.usage_by_single_menu(GEO_NODE_CURVE_RESAMPLE_COUNT);
b.add_input<decl::Float>("Length"_ustr)
.default_value(0.1f)
.min(0.01f)
.subtype(PROP_DISTANCE)
.evaluated_geometry_field()
.usage_by_single_menu(GEO_NODE_CURVE_RESAMPLE_LENGTH);
}
static void node_layout_ex(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "keep_last_segment", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurveResample *data = MEM_new<NodeGeometryCurveResample>(__func__);
data->keep_last_segment = true;
node->storage = data;
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"_ustr);
const auto mode = params.extract_input<GeometryNodeCurveResampleMode>("Mode"_ustr);
const NodeGeometryCurveResample &storage = node_storage(params.node());
const Field<bool> selection = params.extract_input<Field<bool>>("Selection"_ustr);
GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set);
switch (mode) {
case GEO_NODE_CURVE_RESAMPLE_COUNT: {
Field<int> count = params.extract_input<Field<int>>("Count"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry) {
if (const Curves *src_curves_id = geometry.get_curves()) {
const bke::CurvesGeometry &src_curves = src_curves_id->geometry.wrap();
const bke::CurvesFieldContext field_context{*src_curves_id, AttrDomain::Curve};
bke::CurvesGeometry dst_curves = geometry::resample_to_count(
src_curves, field_context, selection, count);
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
bke::curves_copy_parameters(*src_curves_id, *dst_curves_id);
geometry.replace_curves(dst_curves_id);
}
if (GreasePencil *grease_pencil = geometry.get_grease_pencil_for_write()) {
using namespace blender::bke::greasepencil;
for (const int layer_index : grease_pencil->layers().index_range()) {
Drawing *drawing = grease_pencil->get_eval_drawing(grease_pencil->layer(layer_index));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &src_curves = drawing->strokes();
const bke::GreasePencilLayerFieldContext field_context(
*grease_pencil, AttrDomain::Curve, layer_index);
bke::CurvesGeometry dst_curves = geometry::resample_to_count(
src_curves, field_context, selection, count);
drawing->strokes_for_write() = std::move(dst_curves);
drawing->tag_topology_changed();
}
}
});
break;
}
case GEO_NODE_CURVE_RESAMPLE_LENGTH: {
Field<float> length = params.extract_input<Field<float>>("Length"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry) {
if (const Curves *src_curves_id = geometry.get_curves()) {
const bke::CurvesGeometry &src_curves = src_curves_id->geometry.wrap();
const bke::CurvesFieldContext field_context{*src_curves_id, AttrDomain::Curve};
bke::CurvesGeometry dst_curves = geometry::resample_to_length(
src_curves, field_context, selection, length, {}, storage.keep_last_segment);
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
bke::curves_copy_parameters(*src_curves_id, *dst_curves_id);
geometry.replace_curves(dst_curves_id);
}
if (GreasePencil *grease_pencil = geometry.get_grease_pencil_for_write()) {
using namespace blender::bke::greasepencil;
for (const int layer_index : grease_pencil->layers().index_range()) {
Drawing *drawing = grease_pencil->get_eval_drawing(grease_pencil->layer(layer_index));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &src_curves = drawing->strokes();
const bke::GreasePencilLayerFieldContext field_context(
*grease_pencil, AttrDomain::Curve, layer_index);
bke::CurvesGeometry dst_curves = geometry::resample_to_length(
src_curves, field_context, selection, length, {}, storage.keep_last_segment);
drawing->strokes_for_write() = std::move(dst_curves);
drawing->tag_topology_changed();
}
}
});
break;
}
case GEO_NODE_CURVE_RESAMPLE_EVALUATED:
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry) {
if (const Curves *src_curves_id = geometry.get_curves()) {
const bke::CurvesGeometry &src_curves = src_curves_id->geometry.wrap();
const bke::CurvesFieldContext field_context{*src_curves_id, AttrDomain::Curve};
bke::CurvesGeometry dst_curves = geometry::resample_to_evaluated(
src_curves, field_context, selection);
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
bke::curves_copy_parameters(*src_curves_id, *dst_curves_id);
geometry.replace_curves(dst_curves_id);
}
if (GreasePencil *grease_pencil = geometry.get_grease_pencil_for_write()) {
using namespace blender::bke::greasepencil;
for (const int layer_index : grease_pencil->layers().index_range()) {
Drawing *drawing = grease_pencil->get_eval_drawing(grease_pencil->layer(layer_index));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &src_curves = drawing->strokes();
const bke::GreasePencilLayerFieldContext field_context(
*grease_pencil, AttrDomain::Curve, layer_index);
bke::CurvesGeometry dst_curves = geometry::resample_to_evaluated(
src_curves, field_context, selection);
drawing->strokes_for_write() = std::move(dst_curves);
drawing->tag_topology_changed();
}
}
});
break;
}
params.set_output("Curve"_ustr, std::move(geometry_set));
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_boolean(srna,
"keep_last_segment",
"Keep Last Segment",
"Do not collapse curves to single points if they are shorter than the "
"given length. The collapsing behavior exists for compatibility reasons.",
NOD_storage_boolean_accessors(keep_last_segment, 1));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeResampleCurve"_ustr, GEO_NODE_RESAMPLE_CURVE);
ntype.ui_name = "Resample Curve";
ntype.ui_description = "Generate a poly spline for each input spline";
ntype.enum_name_legacy = "RESAMPLE_CURVE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.draw_buttons_ex = node_layout_ex;
bke::node_type_storage(
ntype, "NodeGeometryCurveResample", node_free_standard_storage, node_copy_standard_storage);
ntype.initfunc = node_init;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_resample_cc

View File

@@ -0,0 +1,93 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "GEO_foreach_geometry.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_reverse_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Geometry>("Curve"_ustr)
.supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil})
.description("Curves to switch the start and end of");
b.add_output<decl::Geometry>("Curve"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field();
}
static void reverse_curve(bke::CurvesGeometry &curves,
const fn::FieldContext &field_context,
const Field<bool> &selection_field)
{
fn::FieldEvaluator selection_evaluator{field_context, curves.curves_num()};
selection_evaluator.add(selection_field);
selection_evaluator.evaluate();
const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0);
if (selection.is_empty()) {
return;
}
curves.reverse_curves(selection);
}
static void reverse_grease_pencil(GreasePencil &grease_pencil, const Field<bool> &selection_field)
{
using namespace blender::bke::greasepencil;
for (const int layer_index : grease_pencil.layers().index_range()) {
Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
if (drawing == nullptr) {
continue;
}
bke::CurvesGeometry &curves = drawing->strokes_for_write();
const bke::GreasePencilLayerFieldContext field_context(
grease_pencil, AttrDomain::Curve, layer_index);
reverse_curve(curves, field_context, selection_field);
drawing->tag_topology_changed();
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"_ustr);
Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"_ustr);
GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
if (Curves *curves_id = geometry_set.get_curves_for_write()) {
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
const bke::CurvesFieldContext field_context{*curves_id, AttrDomain::Curve};
reverse_curve(curves, field_context, selection_field);
}
if (GreasePencil *grease_pencil = geometry_set.get_grease_pencil_for_write()) {
reverse_grease_pencil(*grease_pencil, selection_field);
}
});
params.set_output("Curve"_ustr, std::move(geometry_set));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeReverseCurve"_ustr, GEO_NODE_REVERSE_CURVE);
ntype.ui_name = "Reverse Curve";
ntype.ui_description = "Change the direction of curves by swapping their start and end data";
ntype.enum_name_legacy = "REVERSE_CURVE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_reverse_cc

View File

@@ -0,0 +1,605 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_color.hh"
#include "BLI_math_quaternion.hh"
#include "BLI_math_vector.hh"
#include "BLI_generic_array.hh"
#include "BLI_length_parameterize.hh"
#include "BKE_curves.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_sample_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurveSample)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Curves"_ustr)
.only_realized_data()
.supported_type(GeometryComponent::Type::Curve)
.description("Curves to sample positions on");
if (const bNode *node = b.node_or_null()) {
const NodeGeometryCurveSample &storage = node_storage(*node);
b.add_input(eCustomDataType(storage.data_type), "Value"_ustr)
.hide_value()
.evaluated_geometry_field();
}
auto &factor = b.add_input<decl::Float>("Factor"_ustr)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.structure_type(StructureType::Dynamic)
.make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR;
});
auto &length = b.add_input<decl::Float>("Length"_ustr)
.min(0.0f)
.subtype(PROP_DISTANCE)
.structure_type(StructureType::Dynamic)
.make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH;
});
auto &index = b.add_input<decl::Int>("Curve Index"_ustr)
.structure_type(StructureType::Dynamic)
.make_available(
[](bNode &node) { node_storage(node).use_all_curves = false; });
const std::array<int, 3> dynamic_inputs = {factor.index(), length.index(), index.index()};
if (const bNode *node = b.node_or_null()) {
const NodeGeometryCurveSample &storage = node_storage(*node);
const GeometryNodeCurveSampleMode mode = GeometryNodeCurveSampleMode(storage.mode);
b.add_output(eCustomDataType(storage.data_type), "Value"_ustr)
.propagate_references(dynamic_inputs)
.inferred_structure_type(dynamic_inputs);
factor.available(mode == GEO_NODE_CURVE_SAMPLE_FACTOR);
length.available(mode == GEO_NODE_CURVE_SAMPLE_LENGTH);
index.available(!storage.use_all_curves);
}
b.add_output<decl::Vector>("Position"_ustr)
.propagate_references(dynamic_inputs)
.inferred_structure_type(dynamic_inputs);
b.add_output<decl::Vector>("Tangent"_ustr)
.propagate_references(dynamic_inputs)
.inferred_structure_type(dynamic_inputs);
b.add_output<decl::Vector>("Normal"_ustr)
.propagate_references(dynamic_inputs)
.inferred_structure_type(dynamic_inputs);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
layout.prop(ptr, "mode", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
layout.prop(ptr, "use_all_curves", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurveSample *data = MEM_new<NodeGeometryCurveSample>(__func__);
data->mode = GEO_NODE_CURVE_SAMPLE_FACTOR;
data->use_all_curves = false;
data->data_type = CD_PROP_FLOAT;
node->storage = data;
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const NodeDeclaration &declaration = *params.node_type().static_declaration;
search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(1));
search_link_ops_for_declarations(params, declaration.inputs.as_span().take_back(3));
search_link_ops_for_declarations(params, declaration.outputs.as_span().take_back(3));
const std::optional<eCustomDataType> type = bke::socket_type_to_custom_data_type(
params.other_socket().type);
if (type && *type != CD_PROP_STRING) {
/* The input and output sockets have the same name. */
params.add_item(IFACE_("Value"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeSampleCurve"_ustr);
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Value"_ustr);
});
}
}
static void sample_indices_and_lengths(const Span<float> accumulated_lengths,
const Span<float> sample_lengths,
const GeometryNodeCurveSampleMode length_mode,
const IndexMask &mask,
MutableSpan<int> r_segment_indices,
MutableSpan<float> r_length_in_segment)
{
const float total_length = accumulated_lengths.last();
length_parameterize::SampleSegmentHint hint;
mask.foreach_index([&](const int i) {
const float sample_length = length_mode == GEO_NODE_CURVE_SAMPLE_FACTOR ?
sample_lengths[i] * total_length :
sample_lengths[i];
int segment_i;
float factor_in_segment;
length_parameterize::sample_at_length(accumulated_lengths,
std::clamp(sample_length, 0.0f, total_length),
segment_i,
factor_in_segment,
&hint);
const float segment_start = segment_i == 0 ? 0.0f : accumulated_lengths[segment_i - 1];
const float segment_end = accumulated_lengths[segment_i];
const float segment_length = segment_end - segment_start;
r_segment_indices[i] = segment_i;
r_length_in_segment[i] = factor_in_segment * segment_length;
});
}
static void sample_indices_and_factors_to_compressed(const Span<float> accumulated_lengths,
const Span<float> sample_lengths,
const GeometryNodeCurveSampleMode length_mode,
const IndexMask &mask,
MutableSpan<int> r_segment_indices,
MutableSpan<float> r_factor_in_segment)
{
const float total_length = accumulated_lengths.last();
length_parameterize::SampleSegmentHint hint;
switch (length_mode) {
case GEO_NODE_CURVE_SAMPLE_FACTOR:
mask.foreach_index([&](const int i, const int pos) {
const float length = sample_lengths[i] * total_length;
length_parameterize::sample_at_length(accumulated_lengths,
std::clamp(length, 0.0f, total_length),
r_segment_indices[pos],
r_factor_in_segment[pos],
&hint);
});
break;
case GEO_NODE_CURVE_SAMPLE_LENGTH:
mask.foreach_index([&](const int i, const int pos) {
const float length = sample_lengths[i];
length_parameterize::sample_at_length(accumulated_lengths,
std::clamp(length, 0.0f, total_length),
r_segment_indices[pos],
r_factor_in_segment[pos],
&hint);
});
break;
}
}
/**
* Given an array of accumulated lengths, find the segment indices that
* sample lengths lie on, and how far along the segment they are.
*/
class SampleFloatSegmentsFunction : public mf::MultiFunction {
private:
Array<float> accumulated_lengths_;
GeometryNodeCurveSampleMode length_mode_;
public:
SampleFloatSegmentsFunction(Array<float> accumulated_lengths,
const GeometryNodeCurveSampleMode length_mode)
: accumulated_lengths_(std::move(accumulated_lengths)), length_mode_(length_mode)
{
static const mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Sample Curve Index", signature};
builder.single_input<float>("Length");
builder.single_output<int>("Curve Index");
builder.single_output<float>("Length in Curve");
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArraySpan<float> lengths = params.readonly_single_input<float>(0, "Length");
MutableSpan<int> indices = params.uninitialized_single_output<int>(1, "Curve Index");
MutableSpan<float> lengths_in_segments = params.uninitialized_single_output<float>(
2, "Length in Curve");
sample_indices_and_lengths(
accumulated_lengths_, lengths, length_mode_, mask, indices, lengths_in_segments);
}
};
class SampleCurveFunction : public mf::MultiFunction {
private:
/**
* The function holds a geometry set instead of curves or a curve component reference in order
* to maintain a ownership of the geometry while the field tree is being built and used, so
* that the curve is not freed before the function can execute.
*/
GeometrySet geometry_set_;
GField src_field_;
GeometryNodeCurveSampleMode length_mode_;
mf::Signature signature_;
mutable CacheMutex mutex_;
mutable std::optional<bke::CurvesFieldContext> source_context_;
mutable std::unique_ptr<FieldEvaluator> source_evaluator_;
mutable const GVArray *source_data_;
public:
SampleCurveFunction(GeometrySet geometry_set,
const GeometryNodeCurveSampleMode length_mode,
GField src_field)
: geometry_set_(std::move(geometry_set)),
src_field_(std::move(src_field)),
length_mode_(length_mode)
{
mf::SignatureBuilder builder{"Sample Curve", signature_};
builder.single_input<int>("Curve Index");
builder.single_input<float>("Length");
builder.single_output<float3>("Position", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float3>("Tangent", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float3>("Normal", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output("Value", src_field_.cpp_type(), mf::ParamFlag::SupportsUnusedOutput);
this->set_signature(&signature_);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
MutableSpan<float3> sampled_positions = params.uninitialized_single_output_if_required<float3>(
2, "Position");
MutableSpan<float3> sampled_tangents = params.uninitialized_single_output_if_required<float3>(
3, "Tangent");
MutableSpan<float3> sampled_normals = params.uninitialized_single_output_if_required<float3>(
4, "Normal");
GMutableSpan sampled_values = params.uninitialized_single_output_if_required(5, "Value");
auto return_default = [&]() {
if (!sampled_positions.is_empty()) {
index_mask::masked_fill(sampled_positions, {0, 0, 0}, mask);
}
if (!sampled_tangents.is_empty()) {
index_mask::masked_fill(sampled_tangents, {0, 0, 0}, mask);
}
if (!sampled_normals.is_empty()) {
index_mask::masked_fill(sampled_normals, {0, 0, 0}, mask);
}
};
if (!geometry_set_.has_curves()) {
return_default();
return;
}
const Curves &curves_id = *geometry_set_.get_curves();
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
if (curves.is_empty()) {
return_default();
return;
}
curves.ensure_can_interpolate_to_evaluated();
Span<float3> evaluated_positions = curves.evaluated_positions();
Span<float3> evaluated_tangents;
Span<float3> evaluated_normals;
if (!sampled_tangents.is_empty()) {
evaluated_tangents = curves.evaluated_tangents();
}
if (!sampled_normals.is_empty()) {
evaluated_normals = curves.evaluated_normals();
}
const OffsetIndices points_by_curve = curves.points_by_curve();
const OffsetIndices evaluated_points_by_curve = curves.evaluated_points_by_curve();
const VArray<int> curve_indices = params.readonly_single_input<int>(0, "Curve Index");
const VArraySpan<float> lengths = params.readonly_single_input<float>(1, "Length");
const VArray<bool> cyclic = curves.cyclic();
Array<int> indices;
Array<float> factors;
GArray<> src_original_values(source_data_->type());
GArray<> src_evaluated_values(source_data_->type());
auto fill_invalid = [&](const IndexMask &mask) {
if (!sampled_positions.is_empty()) {
index_mask::masked_fill(sampled_positions, float3(0), mask);
}
if (!sampled_tangents.is_empty()) {
index_mask::masked_fill(sampled_tangents, float3(0), mask);
}
if (!sampled_normals.is_empty()) {
index_mask::masked_fill(sampled_normals, float3(0), mask);
}
if (!sampled_values.is_empty()) {
sampled_values.type().fill_construct_indices(
sampled_values.type().default_value(), sampled_values.data(), mask);
}
};
auto sample_curve = [&](const int curve_i, const IndexMask &mask) {
const IndexRange evaluated_points = evaluated_points_by_curve[curve_i];
if (evaluated_points.size() == 1) {
if (!sampled_positions.is_empty()) {
index_mask::masked_fill(
sampled_positions, evaluated_positions[evaluated_points.first()], mask);
}
if (!sampled_tangents.is_empty()) {
index_mask::masked_fill(
sampled_tangents, evaluated_tangents[evaluated_points.first()], mask);
}
if (!sampled_normals.is_empty()) {
index_mask::masked_fill(
sampled_normals, evaluated_normals[evaluated_points.first()], mask);
}
if (!sampled_values.is_empty()) {
BUFFER_FOR_CPP_TYPE_VALUE(source_data_->type(), value);
source_data_->get(points_by_curve[curve_i].first(), value);
source_data_->type().fill_construct_indices(value, sampled_values.data(), mask);
}
return;
}
const Span<float> accumulated_lengths = curves.evaluated_lengths_for_curve(curve_i,
cyclic[curve_i]);
if (accumulated_lengths.is_empty()) {
/* Sanity check in case of invalid evaluation (for example NURBS with invalid order). */
fill_invalid(mask);
return;
}
/* Store the sampled indices and factors in arrays the size of the mask.
* Then, during interpolation, move the results back to the masked indices. */
indices.reinitialize(mask.size());
factors.reinitialize(mask.size());
sample_indices_and_factors_to_compressed(
accumulated_lengths, lengths, length_mode_, mask, indices, factors);
if (!sampled_positions.is_empty()) {
length_parameterize::interpolate_to_masked<float3>(
evaluated_positions.slice(evaluated_points),
indices,
factors,
mask,
sampled_positions);
}
if (!sampled_tangents.is_empty()) {
length_parameterize::interpolate_to_masked<float3>(
evaluated_tangents.slice(evaluated_points), indices, factors, mask, sampled_tangents);
mask.foreach_index(
[&](const int i) { sampled_tangents[i] = math::normalize(sampled_tangents[i]); });
}
if (!sampled_normals.is_empty()) {
length_parameterize::interpolate_to_masked<float3>(
evaluated_normals.slice(evaluated_points), indices, factors, mask, sampled_normals);
mask.foreach_index(
[&](const int i) { sampled_normals[i] = math::normalize(sampled_normals[i]); });
}
if (!sampled_values.is_empty()) {
const IndexRange points = points_by_curve[curve_i];
src_original_values.reinitialize(points.size());
source_data_->materialize_compressed_to_uninitialized(points, src_original_values.data());
src_evaluated_values.reinitialize(evaluated_points.size());
curves.interpolate_to_evaluated(curve_i, src_original_values, src_evaluated_values);
bke::attribute_math::to_static_type(source_data_->type(), [&]<typename T>() {
if constexpr (!std::is_same_v<T, std::string>) {
const Span<T> src_evaluated_values_typed = src_evaluated_values.as_span().typed<T>();
MutableSpan<T> sampled_values_typed = sampled_values.typed<T>();
length_parameterize::interpolate_to_masked<T>(
src_evaluated_values_typed, indices, factors, mask, sampled_values_typed);
}
});
}
};
if (const std::optional<int> curve_i = curve_indices.get_if_single()) {
if (curves.curves_range().contains(*curve_i)) {
sample_curve(*curve_i, mask);
}
else {
fill_invalid(mask);
}
}
else {
Vector<int> valid_indices;
Vector<int> invalid_indices;
VectorSet<int> used_curves;
devirtualize_varray(curve_indices, [&](const auto curve_indices) {
mask.foreach_index([&](const int i) {
const int curve_i = curve_indices[i];
if (curves.curves_range().contains(curve_i)) {
used_curves.add(curve_i);
valid_indices.append(i);
}
else {
invalid_indices.append(i);
}
});
});
IndexMaskMemory memory;
const IndexMask valid_indices_mask = valid_indices.size() == mask.size() ?
mask :
IndexMask::from_indices(valid_indices.as_span(),
memory);
Array<IndexMask> mask_by_curve(used_curves.size());
IndexMask::from_groups<int>(
valid_indices_mask,
memory,
[&](const int i) { return used_curves.index_of(curve_indices[i]); },
mask_by_curve);
for (const int i : mask_by_curve.index_range()) {
sample_curve(used_curves[i], mask_by_curve[i]);
}
fill_invalid(IndexMask::from_indices<int>(invalid_indices, memory));
}
}
void prepare_for_execution() const override
{
mutex_.ensure([&]() {
const Curves &curves_id = *geometry_set_.get_curves();
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
source_context_.emplace(bke::CurvesFieldContext{curves_id, AttrDomain::Point});
source_evaluator_ = std::make_unique<FieldEvaluator>(*source_context_, curves.points_num());
source_evaluator_->add(src_field_);
source_evaluator_->evaluate();
source_data_ = &source_evaluator_->get_evaluated(0);
});
}
void hash_unique(UniqueHashBytes &hash) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(geometry_set_.get_curves());
hash.add(length_mode_);
fn::FieldHashDeep field_hash;
hash.add(field_hash.ensure(src_field_));
}
};
static Array<float> curve_accumulated_lengths(const bke::CurvesGeometry &curves)
{
Array<float> curve_lengths(curves.curves_num());
const VArray<bool> cyclic = curves.cyclic();
float length = 0.0f;
for (const int i : curves.curves_range()) {
length += curves.evaluated_length_total_for_curve(i, cyclic[i]);
curve_lengths[i] = length;
}
return curve_lengths;
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curves"_ustr);
if (!geometry_set.has_curves()) {
params.set_default_remaining_outputs();
return;
}
const Curves &curves_id = *geometry_set.get_curves();
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
if (curves.is_empty()) {
params.set_default_remaining_outputs();
return;
}
curves.ensure_evaluated_lengths();
const NodeGeometryCurveSample &storage = node_storage(params.node());
const GeometryNodeCurveSampleMode mode = GeometryNodeCurveSampleMode(storage.mode);
const UString length_input_name = mode == GEO_NODE_CURVE_SAMPLE_FACTOR ? "Factor"_ustr :
"Length"_ustr;
auto sample_length = params.extract_input<bke::SocketValueVariant>(length_input_name);
GField src_values_field = params.extract_input<GField>("Value"_ustr);
std::string error_message;
bke::SocketValueVariant position;
bke::SocketValueVariant tangent;
bke::SocketValueVariant normal;
bke::SocketValueVariant value;
std::shared_ptr<FieldOperation> sample_op;
if (curves.curves_num() == 1) {
auto curve_index = bke::SocketValueVariant::From(fn::Field<int>(0));
if (!execute_multi_function_on_value_variant(
std::make_unique<SampleCurveFunction>(
std::move(geometry_set), mode, std::move(src_values_field)),
{&curve_index, &sample_length},
{&position, &tangent, &normal, &value},
params.user_data(),
error_message))
{
params.set_default_remaining_outputs();
params.error_message_add(NodeWarningType::Error, std::move(error_message));
return;
}
}
else {
if (storage.use_all_curves) {
bke::SocketValueVariant curve_index;
bke::SocketValueVariant length_in_curve;
if (!execute_multi_function_on_value_variant(std::make_unique<SampleFloatSegmentsFunction>(
curve_accumulated_lengths(curves), mode),
{&sample_length},
{&curve_index, &length_in_curve},
params.user_data(),
error_message))
{
params.set_default_remaining_outputs();
params.error_message_add(NodeWarningType::Error, std::move(error_message));
return;
}
if (!execute_multi_function_on_value_variant(
std::make_shared<SampleCurveFunction>(std::move(geometry_set),
GEO_NODE_CURVE_SAMPLE_LENGTH,
std::move(src_values_field)),
{&curve_index, &length_in_curve},
{&position, &tangent, &normal, &value},
params.user_data(),
error_message))
{
params.set_default_remaining_outputs();
params.error_message_add(NodeWarningType::Error, std::move(error_message));
return;
}
}
else {
auto curve_index = params.extract_input<bke::SocketValueVariant>("Curve Index"_ustr);
if (!execute_multi_function_on_value_variant(
std::make_shared<SampleCurveFunction>(
std::move(geometry_set), mode, std::move(src_values_field)),
{&curve_index, &sample_length},
{&position, &tangent, &normal, &value},
params.user_data(),
error_message))
{
params.set_default_remaining_outputs();
params.error_message_add(NodeWarningType::Error, std::move(error_message));
return;
}
}
}
params.set_output("Position"_ustr, std::move(position));
params.set_output("Tangent"_ustr, std::move(tangent));
params.set_output("Normal"_ustr, std::move(normal));
params.set_output("Value"_ustr, std::move(value));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeSampleCurve"_ustr, GEO_NODE_SAMPLE_CURVE);
ntype.ui_name = "Sample Curve";
ntype.ui_description =
"Retrieve data from a point on a curve at a certain distance from its start";
ntype.enum_name_legacy = "SAMPLE_CURVE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
ntype.initfunc = node_init;
bke::node_type_storage(
ntype, "NodeGeometryCurveSample", node_free_standard_storage, node_copy_standard_storage);
ntype.draw_buttons = node_layout;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_sample_cc

View File

@@ -0,0 +1,151 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <atomic>
#include "BKE_curves.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "GEO_foreach_geometry.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_set_handle_type_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurveSetHandles)
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
b.add_input<decl::Geometry>("Curve"_ustr)
.supported_type(GeometryComponent::Type::Curve)
.description("Curves to set handles of control points on");
b.add_output<decl::Geometry>("Curve"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field();
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
layout.prop(ptr, "handle_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurveSetHandles *data = MEM_new<NodeGeometryCurveSetHandles>(__func__);
data->handle_type = GEO_NODE_CURVE_HANDLE_AUTO;
data->mode = GEO_NODE_CURVE_HANDLE_LEFT | GEO_NODE_CURVE_HANDLE_RIGHT;
node->storage = data;
}
static HandleType handle_type_from_input_type(GeometryNodeCurveHandleType type)
{
switch (type) {
case GEO_NODE_CURVE_HANDLE_AUTO:
return BEZIER_HANDLE_AUTO;
case GEO_NODE_CURVE_HANDLE_ALIGN:
return BEZIER_HANDLE_ALIGN;
case GEO_NODE_CURVE_HANDLE_FREE:
return BEZIER_HANDLE_FREE;
case GEO_NODE_CURVE_HANDLE_VECTOR:
return BEZIER_HANDLE_VECTOR;
}
BLI_assert_unreachable();
return BEZIER_HANDLE_AUTO;
}
static void set_handle_type(Curves &curves_id,
const GeometryNodeCurveHandleMode mode,
const HandleType new_handle_type,
const Field<bool> &selection_field)
{
bke::CurvesGeometry &curves = curves_id.geometry.wrap();
const bke::CurvesFieldContext field_context{curves_id, AttrDomain::Point};
fn::FieldEvaluator evaluator{field_context, curves.points_num()};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
if (mode & GEO_NODE_CURVE_HANDLE_LEFT) {
index_mask::masked_fill<int8_t>(
curves.handle_types_left_for_write(), new_handle_type, selection);
}
if (mode & GEO_NODE_CURVE_HANDLE_RIGHT) {
index_mask::masked_fill<int8_t>(
curves.handle_types_right_for_write(), new_handle_type, selection);
}
curves.tag_topology_changed();
/* Eagerly calculate automatically derived handle positions if necessary. */
if (ELEM(new_handle_type, BEZIER_HANDLE_AUTO, BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_ALIGN)) {
curves.calculate_bezier_auto_handles();
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryCurveSetHandles &storage = node_storage(params.node());
const GeometryNodeCurveHandleType type = (GeometryNodeCurveHandleType)storage.handle_type;
const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)storage.mode;
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"_ustr);
Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"_ustr);
const HandleType new_handle_type = handle_type_from_input_type(type);
std::atomic<bool> has_curves = false;
std::atomic<bool> has_bezier = false;
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
if (Curves *curves_id = geometry_set.get_curves_for_write()) {
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
has_curves = true;
const AttributeAccessor attributes = curves.attributes();
if (!attributes.contains("handle_type_left") || !attributes.contains("handle_type_right")) {
return;
}
has_bezier = true;
set_handle_type(*curves_id, mode, new_handle_type, selection_field);
}
});
if (has_curves && !has_bezier) {
params.error_message_add(NodeWarningType::Info, TIP_("Input curves do not have Bézier type"));
}
params.set_output("Curve"_ustr, std::move(geometry_set));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCurveSetHandles"_ustr, GEO_NODE_CURVE_SET_HANDLE_TYPE);
ntype.ui_name = "Set Handle Type";
ntype.ui_description = "Set the handle type for the control points of a Bézier curve";
ntype.enum_name_legacy = "CURVE_SET_HANDLES";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.initfunc = node_init;
bke::node_type_storage(ntype,
"NodeGeometryCurveSetHandles",
node_free_standard_storage,
node_copy_standard_storage);
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_set_handle_type_cc

View File

@@ -0,0 +1,292 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_task.hh"
#include "BKE_curves.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_spline_parameter_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Float>("Factor"_ustr)
.structure_type(StructureType::Field)
.description(
"For points, the portion of the spline's total length at the control point. For "
"Splines, the factor of that spline within the entire curve");
b.add_output<decl::Float>("Length"_ustr)
.structure_type(StructureType::Field)
.description(
"For points, the distance along the control point's spline, For splines, the "
"distance along the entire curve");
b.add_output<decl::Int>("Index"_ustr)
.structure_type(StructureType::Field)
.description("Each control point's index on its spline");
}
/**
* For lengths on the curve domain, a basic interpolation from the point domain would be useless,
* since the average parameter for each curve would just be 0.5, or close to it. Instead, the
* value for each curve is defined as the portion of the total length of all curves at its start.
*/
static Array<float> accumulated_lengths_curve_domain(const bke::CurvesGeometry &curves)
{
curves.ensure_evaluated_lengths();
Array<float> lengths(curves.curves_num());
const VArray<bool> cyclic = curves.cyclic();
float length = 0.0f;
for (const int i : curves.curves_range()) {
lengths[i] = length;
length += curves.evaluated_length_total_for_curve(i, cyclic[i]);
}
return lengths;
}
static Array<float> calculate_curve_parameters(const bke::CurvesGeometry &curves)
{
const VArray<bool> cyclic = curves.cyclic();
Array<float> lengths = accumulated_lengths_curve_domain(curves);
const int last_index = curves.curves_num() - 1;
const float total_length = lengths.last() + curves.evaluated_length_total_for_curve(
last_index, cyclic[last_index]);
if (total_length > 0.0f) {
const float factor = 1.0f / total_length;
for (float &value : lengths) {
value *= factor;
}
}
else {
/* It is arbitrary what to do in those rare cases when all the points are
* in the same position. In this case we are just arbitrarily giving a valid
* value in the range based on the curve index. */
for (const int i : lengths.index_range()) {
lengths[i] = i / (lengths.size() - 1.0f);
}
}
return lengths;
}
/**
* Return the length of each control point along each curve, starting at zero for the first point.
* Importantly, this is different than the length at each evaluated point. The implementation is
* different for every curve type:
* - Catmull Rom Curves: Use the resolution to find the evaluated point for each control point.
* - Poly Curves: Copy the evaluated lengths, but we need to add a zero to the front of the array.
* - Bezier Curves: Use the evaluated offsets to find the evaluated point for each control point.
* - NURBS Curves: Treat the control points as if they were a poly curve, because there
* is no obvious mapping from each control point to a specific evaluated point.
*/
static Array<float> calculate_point_lengths(
const bke::CurvesGeometry &curves,
const FunctionRef<void(MutableSpan<float>, float)> postprocess_lengths_for_curve)
{
curves.ensure_evaluated_lengths();
const OffsetIndices points_by_curve = curves.points_by_curve();
const VArray<int8_t> types = curves.curve_types();
const VArray<int> resolutions = curves.resolution();
const VArray<bool> cyclic = curves.cyclic();
Array<float> result(curves.points_num());
threading::parallel_for(curves.curves_range(), 128, [&](IndexRange range) {
for (const int i_curve : range) {
const IndexRange points = points_by_curve[i_curve];
const bool is_cyclic = cyclic[i_curve];
const Span<float> evaluated_lengths = curves.evaluated_lengths_for_curve(i_curve, is_cyclic);
MutableSpan<float> lengths = result.as_mutable_span().slice(points);
lengths.first() = 0.0f;
const float last_evaluated_length = evaluated_lengths.is_empty() ? 0.0f :
evaluated_lengths.last();
float total;
switch (types[i_curve]) {
case CURVE_TYPE_CATMULL_ROM: {
const int resolution = resolutions[i_curve];
for (const int i : IndexRange(points.size()).drop_back(1)) {
lengths[i + 1] = evaluated_lengths[resolution * (i + 1) - 1];
}
total = last_evaluated_length;
break;
}
case CURVE_TYPE_POLY:
lengths.drop_front(1).copy_from(evaluated_lengths.take_front(lengths.size() - 1));
total = last_evaluated_length;
break;
case CURVE_TYPE_BEZIER: {
const Span<int> offsets = curves.bezier_evaluated_offsets_for_curve(i_curve);
for (const int i : IndexRange(points.size()).drop_back(1)) {
lengths[i + 1] = evaluated_lengths[offsets[i + 1] - 1];
}
total = last_evaluated_length;
break;
}
case CURVE_TYPE_NURBS: {
const Span<float3> positions = curves.positions().slice(points);
float length = 0.0f;
for (const int i : positions.index_range().drop_back(1)) {
lengths[i] = length;
length += math::distance(positions[i], positions[i + 1]);
}
lengths.last() = length;
if (is_cyclic) {
length += math::distance(positions.first(), positions.last());
}
total = length;
break;
}
}
postprocess_lengths_for_curve(lengths, total);
}
});
return result;
}
static void convert_lengths_to_factors(MutableSpan<float> lengths, const float total_curve_length)
{
if (total_curve_length > 0.0f) {
const float factor = 1.0f / total_curve_length;
for (float &value : lengths.drop_front(1)) {
value *= factor;
}
}
else if (lengths.size() == 1) {
/* The curve is a single point. */
lengths[0] = 0.0f;
}
else {
/* It is arbitrary what to do in those rare cases when all the
* points are in the same position. In this case we are just
* arbitrarily giving a valid
* value in the range based on the point index. */
for (const int i : lengths.index_range()) {
lengths[i] = i / (lengths.size() - 1.0f);
}
}
}
static Array<float> calculate_point_parameters(const bke::CurvesGeometry &curves)
{
return calculate_point_lengths(curves, convert_lengths_to_factors);
}
class CurveParameterFieldInput final : public bke::CurvesFieldInput {
public:
CurveParameterFieldInput()
: bke::CurvesFieldInput(CPPType::get<float>(), "Spline Parameter node")
{
}
GVArray get_varray_for_context(const bke::CurvesGeometry &curves,
const AttrDomain domain,
const IndexMask & /*mask*/) const final
{
switch (domain) {
case AttrDomain::Point:
return VArray<float>::from_container(calculate_point_parameters(curves));
case AttrDomain::Curve:
return VArray<float>::from_container(calculate_curve_parameters(curves));
default:
BLI_assert_unreachable();
return {};
}
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep & /*deep_hash_cache*/) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
}
};
class CurveLengthParameterFieldInput final : public bke::CurvesFieldInput {
public:
CurveLengthParameterFieldInput()
: bke::CurvesFieldInput(CPPType::get<float>(), "Curve Length node")
{
}
GVArray get_varray_for_context(const bke::CurvesGeometry &curves,
const AttrDomain domain,
const IndexMask & /*mask*/) const final
{
switch (domain) {
case AttrDomain::Point:
return VArray<float>::from_container(calculate_point_lengths(
curves, [](MutableSpan<float> /*lengths*/, const float /*total*/) {}));
case AttrDomain::Curve:
return VArray<float>::from_container(accumulated_lengths_curve_domain(curves));
default:
BLI_assert_unreachable();
return {};
}
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep & /*deep_hash_cache*/) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
}
};
class IndexOnSplineFieldInput final : public bke::CurvesFieldInput {
public:
IndexOnSplineFieldInput() : bke::CurvesFieldInput(CPPType::get<int>(), "Spline Index") {}
GVArray get_varray_for_context(const bke::CurvesGeometry &curves,
const AttrDomain domain,
const IndexMask & /*mask*/) const final
{
if (domain != AttrDomain::Point) {
return {};
}
Array<int> result(curves.points_num());
const OffsetIndices points_by_curve = curves.points_by_curve();
threading::parallel_for(curves.curves_range(), 1024, [&](IndexRange range) {
for (const int i_curve : range) {
MutableSpan<int> indices = result.as_mutable_span().slice(points_by_curve[i_curve]);
array_utils::fill_index_range(indices);
}
});
return VArray<int>::from_container(std::move(result));
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep & /*deep_hash_cache*/) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
}
std::optional<AttrDomain> preferred_domain(const bke::CurvesGeometry & /*curves*/) const final
{
return AttrDomain::Point;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
params.set_output("Factor"_ustr, Field<float>::from_input<CurveParameterFieldInput>());
params.set_output("Length"_ustr, Field<float>::from_input<CurveLengthParameterFieldInput>());
params.set_output("Index"_ustr, Field<int>::from_input<IndexOnSplineFieldInput>());
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeSplineParameter"_ustr, GEO_NODE_CURVE_SPLINE_PARAMETER);
ntype.ui_name = "Spline Parameter";
ntype.ui_description = "Retrieve how far along each spline a control point is";
ntype.enum_name_legacy = "SPLINE_PARAMETER";
ntype.nclass = NODE_CLASS_INPUT;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_spline_parameter_cc

View File

@@ -0,0 +1,124 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_set_curve_type.hh"
#include "RNA_enum_types.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_spline_type_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurveSplineType)
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
b.add_input<decl::Geometry>("Curve"_ustr)
.supported_type(GeometryComponent::Type::Curve)
.description("Curves to change the type of");
b.add_output<decl::Geometry>("Curve"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field();
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "spline_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurveSplineType *data = MEM_new<NodeGeometryCurveSplineType>(__func__);
data->spline_type = CURVE_TYPE_POLY;
node->storage = data;
}
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryCurveSplineType &storage = node_storage(params.node());
const CurveType dst_type = CurveType(storage.spline_type);
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"_ustr);
Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
if (!geometry_set.has_curves()) {
return;
}
const Curves &src_curves_id = *geometry_set.get_curves();
const bke::CurvesGeometry &src_curves = src_curves_id.geometry.wrap();
if (src_curves.is_single_type(dst_type)) {
return;
}
const bke::CurvesFieldContext field_context{src_curves_id, AttrDomain::Curve};
fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
if (selection.is_empty()) {
return;
}
bke::CurvesGeometry dst_curves = geometry::convert_curves(
src_curves, selection, dst_type, params.get_attribute_filter("Curve"_ustr));
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
bke::curves_copy_parameters(src_curves_id, *dst_curves_id);
geometry_set.replace_curves(dst_curves_id);
});
params.set_output("Curve"_ustr, std::move(geometry_set));
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"spline_type",
"Type",
"The curve type to change the selected curves to",
rna_enum_curves_type_items,
NOD_storage_enum_accessors(spline_type),
CURVE_TYPE_POLY,
nullptr,
true);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCurveSplineType"_ustr, GEO_NODE_CURVE_SPLINE_TYPE);
ntype.ui_name = "Set Spline Type";
ntype.ui_description = "Change the type of curves";
ntype.enum_name_legacy = "CURVE_SPLINE_TYPE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.initfunc = node_init;
bke::node_type_storage(ntype,
"NodeGeometryCurveSplineType",
node_free_standard_storage,
node_copy_standard_storage);
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_spline_type_cc

View File

@@ -0,0 +1,126 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_subdivide_curves.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_subdivide_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Geometry>("Curve"_ustr)
.supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil})
.description("Curves to subdivide");
b.add_output<decl::Geometry>("Curve"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Int>("Cuts"_ustr)
.default_value(1)
.min(0)
.max(1000)
.evaluated_geometry_field()
.description("The number of control points to create on the segment following each point");
}
static Curves *subdivide_curves(const Curves &src_curves_id,
Field<int> &cuts_field,
const bke::AttributeFilter &attribute_filter)
{
const bke::CurvesGeometry &src_curves = src_curves_id.geometry.wrap();
const bke::CurvesFieldContext field_context{src_curves_id, AttrDomain::Point};
fn::FieldEvaluator evaluator{field_context, src_curves.points_num()};
evaluator.add(cuts_field);
evaluator.evaluate();
const VArray<int> cuts = evaluator.get_evaluated<int>(0);
if (cuts.is_single() && cuts.get_internal_single() < 1) {
return nullptr;
}
bke::CurvesGeometry dst_curves = geometry::subdivide_curves(
src_curves, src_curves.curves_range(), cuts, attribute_filter);
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
bke::curves_copy_parameters(src_curves_id, *dst_curves_id);
return dst_curves_id;
}
static void subdivide_grease_pencil_curves(GreasePencil &grease_pencil,
Field<int> &cuts_field,
const AttributeFilter &attribute_filter)
{
using namespace bke::greasepencil;
for (const int layer_index : grease_pencil.layers().index_range()) {
Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &src_curves = drawing->strokes();
const bke::GreasePencilLayerFieldContext field_context{
grease_pencil, AttrDomain::Point, layer_index};
fn::FieldEvaluator evaluator{field_context, src_curves.points_num()};
evaluator.add(cuts_field);
evaluator.evaluate();
const VArray<int> cuts = evaluator.get_evaluated<int>(0);
if (cuts.is_single() && cuts.get_internal_single() < 1) {
continue;
}
bke::CurvesGeometry dst_curves = geometry::subdivide_curves(
src_curves, src_curves.curves_range(), cuts, attribute_filter);
drawing->strokes_for_write() = std::move(dst_curves);
drawing->tag_topology_changed();
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"_ustr);
Field<int> cuts_field = params.extract_input<Field<int>>("Cuts"_ustr);
GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set);
const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Curve"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
if (geometry_set.has_curves()) {
const Curves &src_curves_id = *geometry_set.get_curves();
Curves *dst_curves_id = subdivide_curves(src_curves_id, cuts_field, attribute_filter);
if (dst_curves_id) {
geometry_set.replace_curves(dst_curves_id);
}
}
if (geometry_set.has_grease_pencil()) {
GreasePencil &grease_pencil = *geometry_set.get_grease_pencil_for_write();
subdivide_grease_pencil_curves(grease_pencil, cuts_field, attribute_filter);
}
});
params.set_output("Curve"_ustr, geometry_set);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeSubdivideCurve"_ustr, GEO_NODE_SUBDIVIDE_CURVE);
ntype.ui_name = "Subdivide Curve";
ntype.ui_description = "Dividing each curve segment into a specified number of pieces";
ntype.enum_name_legacy = "SUBDIVIDE_CURVE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_subdivide_cc

View File

@@ -0,0 +1,174 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "BKE_curve_to_mesh.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_join_geometries.hh"
#include "GEO_randomize.hh"
#include "DNA_mesh_types.h"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_to_mesh_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Curve"_ustr)
.supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil})
.description("Curve to convert to a mesh using the given profile");
b.add_input<decl::Geometry>("Profile Curve"_ustr)
.only_realized_data()
.supported_type(GeometryComponent::Type::Curve)
.description("Curves that are swept along the main curve");
b.add_input<decl::Float>("Scale"_ustr)
.default_value(1.0f)
.min(0.0f)
.evaluated_geometry_field({0})
.description("Scale of the profile at each point");
b.add_input<decl::Bool>("Fill Caps"_ustr)
.description(
"If the profile spline is cyclic, fill the ends of the generated mesh with N-gons");
b.add_output<decl::Geometry>("Mesh"_ustr).propagate_all_geometry();
}
static Mesh *curve_to_mesh(const bke::CurvesGeometry &curves,
const GeometrySet &profile_set,
const fn::FieldContext &context,
const Field<float> &scale_field,
const bool fill_caps,
const AttributeFilter &attribute_filter)
{
Mesh *mesh;
if (profile_set.has_curves()) {
const Curves *profile_curves = profile_set.get_curves();
FieldEvaluator evaluator{context, curves.points_num()};
evaluator.add(scale_field);
evaluator.evaluate();
const VArray<float> profile_scales = evaluator.get_evaluated<float>(0);
mesh = bke::curve_to_mesh_sweep(
curves, profile_curves->geometry.wrap(), profile_scales, fill_caps, attribute_filter);
}
else {
mesh = bke::curve_to_wire_mesh(curves, attribute_filter);
}
geometry::debug_randomize_mesh_order(mesh);
return mesh;
}
static void grease_pencil_to_mesh(GeometrySet &geometry_set,
const GeometrySet &profile_set,
const Field<float> &scale_field,
const bool fill_caps,
const AttributeFilter &attribute_filter)
{
using namespace blender::bke::greasepencil;
const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil();
Array<Mesh *> mesh_by_layer(grease_pencil.layers().size(), nullptr);
for (const int layer_index : grease_pencil.layers().index_range()) {
const Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &curves = drawing->strokes();
const bke::GreasePencilLayerFieldContext context{
grease_pencil, bke::AttrDomain::Point, layer_index};
mesh_by_layer[layer_index] = curve_to_mesh(
curves, profile_set, context, scale_field, fill_caps, attribute_filter);
}
if (mesh_by_layer.is_empty()) {
return;
}
auto instances = std::make_unique<bke::Instances>(mesh_by_layer.size());
MutableSpan<int> handles = instances->reference_handles_for_write();
instances->transforms_for_write().fill(float4x4::identity());
for (const int i : mesh_by_layer.index_range()) {
Mesh *mesh = mesh_by_layer[i];
if (!mesh) {
/* Add an empty reference so the number of layers and instances match.
* This makes it easy to reconstruct the layers afterwards and keep their attributes.
* Although in this particular case we don't propagate the attributes. */
handles[i] = instances->add_reference(bke::InstanceReference());
continue;
}
GeometrySet temp_set = GeometrySet::from_mesh(mesh);
handles[i] = instances->add_reference(bke::InstanceReference{std::move(temp_set)});
}
bke::copy_attributes(geometry_set.get_grease_pencil()->attributes(),
bke::AttrDomain::Layer,
bke::AttrDomain::Instance,
attribute_filter,
instances->attributes_for_write());
InstancesComponent &dst_component = geometry_set.get_component_for_write<InstancesComponent>();
GeometrySet new_instances = geometry::join_geometries(
{GeometrySet::from_instances(dst_component.release()),
GeometrySet::from_instances(std::move(instances))},
attribute_filter);
dst_component.replace(new_instances.get_component_for_write<InstancesComponent>().release());
geometry_set.replace_grease_pencil(nullptr);
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"_ustr);
GeometrySet profile_set = params.extract_input<GeometrySet>("Profile Curve"_ustr);
const Field<float> scale_field = params.extract_input<Field<float>>("Scale"_ustr);
const bool fill_caps = params.extract_input<bool>("Fill Caps"_ustr);
bke::GeometryComponentEditData::remember_deformed_positions_if_necessary(curve_set);
const AttributeFilter &attribute_filter = params.get_attribute_filter("Mesh"_ustr);
geometry::foreach_real_geometry(curve_set, [&](GeometrySet &geometry_set) {
if (geometry_set.has_curves()) {
const Curves &curves = *geometry_set.get_curves();
const bke::CurvesFieldContext context{curves, bke::AttrDomain::Point};
Mesh *mesh = curve_to_mesh(
curves.geometry.wrap(), profile_set, context, scale_field, fill_caps, attribute_filter);
if (mesh != nullptr) {
mesh->mat = MEM_dupalloc(curves.mat);
mesh->totcol = curves.totcol;
}
geometry_set.replace_mesh(mesh);
}
if (geometry_set.has_grease_pencil()) {
grease_pencil_to_mesh(geometry_set, profile_set, scale_field, fill_caps, attribute_filter);
}
geometry_set.keep_only({GeometryComponent::Type::Mesh,
GeometryComponent::Type::Instance,
GeometryComponent::Type::Edit});
});
params.set_output("Mesh"_ustr, std::move(curve_set));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCurveToMesh"_ustr, GEO_NODE_CURVE_TO_MESH);
ntype.ui_name = "Curve to Mesh";
ntype.ui_description =
"Convert curves into a mesh, optionally with a custom profile shape defined by curves";
ntype.enum_name_legacy = "CURVE_TO_MESH";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_to_mesh_cc

View File

@@ -0,0 +1,393 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "DNA_pointcloud_types.h"
#include "BKE_customdata.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_pointcloud.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_join_geometries.hh"
#include "GEO_resample_curves.hh"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_to_points_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurveToPoints)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Curve"_ustr)
.supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil})
.description("Curves to convert to points");
auto &count = b.add_input<decl::Int>("Count"_ustr)
.default_value(10)
.min(2)
.max(100000)
.evaluated_geometry_field()
.make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_COUNT;
});
auto &length = b.add_input<decl::Float>("Length"_ustr)
.default_value(0.1f)
.min(0.001f)
.subtype(PROP_DISTANCE)
.evaluated_geometry_field()
.make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_LENGTH;
});
b.add_output<decl::Geometry>("Points"_ustr).propagate_all_geometry();
b.add_output<decl::Vector>("Tangent"_ustr).anonymous_attribute_output();
b.add_output<decl::Vector>("Normal"_ustr).anonymous_attribute_output();
b.add_output<decl::Rotation>("Rotation"_ustr).anonymous_attribute_output();
const bNode *node = b.node_or_null();
if (node != nullptr) {
const NodeGeometryCurveToPoints &storage = node_storage(*node);
const GeometryNodeCurveResampleMode mode = GeometryNodeCurveResampleMode(storage.mode);
count.available(mode == GEO_NODE_CURVE_RESAMPLE_COUNT);
length.available(mode == GEO_NODE_CURVE_RESAMPLE_LENGTH);
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurveToPoints *data = MEM_new<NodeGeometryCurveToPoints>(__func__);
data->mode = GEO_NODE_CURVE_RESAMPLE_COUNT;
node->storage = data;
}
static void fill_rotation_attribute(const Span<float3> tangents,
const Span<float3> normals,
MutableSpan<math::Quaternion> rotations)
{
threading::parallel_for(IndexRange(rotations.size()), 512, [&](IndexRange range) {
for (const int i : range) {
const float3 tangent = tangents[i];
BLI_assert(math::is_unit(tangent));
const float3 binormal = math::normalize(math::cross(tangent, normals[i]));
const float3 normal = math::cross(binormal, tangent);
rotations[i] = math::to_quaternion(float3x3{normal, binormal, tangent});
}
});
}
static void copy_curve_domain_attributes(const AttributeAccessor curve_attributes,
const AttributeFilter &attribute_filter,
MutableAttributeAccessor point_attributes)
{
curve_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.is_builtin) {
return;
}
if (iter.domain != AttrDomain::Curve) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
if (iter.data_type == bke::AttrType::String) {
return;
}
point_attributes.add(iter.name,
AttrDomain::Point,
iter.data_type,
bke::AttributeInitVArray(*iter.get(AttrDomain::Point)));
});
}
static PointCloud *curves_to_points(
const bke::CurvesGeometry &curves,
const AttributeFilter &attribute_filter,
const geometry::ResampleCurvesOutputAttributeIDs &resample_attributes,
const std::optional<StringRef> &rotation_id)
{
const AttributeAccessor curve_attributes = curves.attributes();
PointCloud *pointcloud = bke::pointcloud_new_no_attributes(curves.points_num());
MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write();
const bke::AttributeFilterFromFunc filter = [&](const StringRef name) {
if (ELEM(name, resample_attributes.tangent_id, resample_attributes.normal_id, rotation_id)) {
return bke::AttributeFilter::Result::Process;
}
if (attribute_filter.allow_skip(name)) {
return bke::AttributeFilter::Result::AllowSkip;
}
if (curve_attributes.is_builtin(name) && !point_attributes.is_builtin(name)) {
return bke::AttributeFilter::Result::AllowSkip;
}
return bke::AttributeFilter::Result::Process;
};
bke::copy_attributes(curves.attributes(),
bke::AttrDomain::Point,
bke::AttrDomain::Point,
filter,
pointcloud->attributes_for_write());
copy_curve_domain_attributes(curve_attributes, filter, point_attributes);
if (rotation_id) {
const VArraySpan tangents = *curve_attributes.lookup<float3>(*resample_attributes.tangent_id,
AttrDomain::Point);
const VArraySpan normals = *curve_attributes.lookup<float3>(*resample_attributes.normal_id,
AttrDomain::Point);
SpanAttributeWriter rotations =
point_attributes.lookup_or_add_for_write_only_span<math::Quaternion>(*rotation_id,
AttrDomain::Point);
fill_rotation_attribute(tangents, normals, rotations.span);
rotations.finish();
}
return pointcloud;
}
static void layer_pointclouds_to_instances(const Span<PointCloud *> pointcloud_by_layer,
const AttributeFilter &attribute_filter,
GeometrySet &geometry)
{
if (!pointcloud_by_layer.is_empty()) {
auto instances = std::make_unique<bke::Instances>(pointcloud_by_layer.size());
MutableSpan<int> handles = instances->reference_handles_for_write();
instances->transforms_for_write().fill(float4x4::identity());
for (const int i : pointcloud_by_layer.index_range()) {
PointCloud *pointcloud = pointcloud_by_layer[i];
if (!pointcloud) {
/* Add an empty reference so the number of layers and instances match.
* This makes it easy to reconstruct the layers afterwards and keep their
* attributes. */
handles[i] = instances->add_reference(bke::InstanceReference());
continue;
}
GeometrySet temp_set = GeometrySet::from_pointcloud(pointcloud);
handles[i] = instances->add_reference(bke::InstanceReference{std::move(temp_set)});
}
bke::copy_attributes(geometry.get_grease_pencil()->attributes(),
bke::AttrDomain::Layer,
bke::AttrDomain::Instance,
attribute_filter,
instances->attributes_for_write());
InstancesComponent &dst_component = geometry.get_component_for_write<InstancesComponent>();
GeometrySet new_instances = geometry::join_geometries(
{GeometrySet::from_instances(dst_component.release()),
GeometrySet::from_instances(std::move(instances))},
attribute_filter);
dst_component.replace(new_instances.get_component_for_write<InstancesComponent>().release());
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryCurveToPoints &storage = node_storage(params.node());
const GeometryNodeCurveResampleMode mode = GeometryNodeCurveResampleMode(storage.mode);
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"_ustr);
GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set);
std::optional<std::string> rotation_anonymous_id =
params.get_output_anonymous_attribute_id_if_needed("Rotation"_ustr);
const bool need_tangent_and_normal = bool(rotation_anonymous_id);
std::optional<std::string> tangent_anonymous_id =
params.get_output_anonymous_attribute_id_if_needed("Tangent"_ustr, need_tangent_and_normal);
std::optional<std::string> normal_anonymous_id =
params.get_output_anonymous_attribute_id_if_needed("Normal"_ustr, need_tangent_and_normal);
geometry::ResampleCurvesOutputAttributeIDs resample_attributes;
resample_attributes.tangent_id = tangent_anonymous_id;
resample_attributes.normal_id = normal_anonymous_id;
const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Points"_ustr);
switch (mode) {
case GEO_NODE_CURVE_RESAMPLE_COUNT: {
const Field<int> count = params.extract_input<Field<int>>("Count"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry) {
if (const Curves *src_curves_id = geometry.get_curves()) {
bke::CurvesGeometry dst_curves = geometry::resample_to_count(
src_curves_id->geometry.wrap(),
bke::CurvesFieldContext(*src_curves_id, AttrDomain::Curve),
fn::Field<bool>(true),
count,
resample_attributes);
PointCloud *pointcloud = curves_to_points(
dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
geometry.replace_pointcloud(pointcloud);
}
if (const GreasePencil *grease_pencil = geometry.get_grease_pencil()) {
Array<PointCloud *> pointcloud_by_layer(grease_pencil->layers().size(), nullptr);
for (const int layer_index : grease_pencil->layers().index_range()) {
const bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(
grease_pencil->layer(layer_index));
if (drawing == nullptr) {
continue;
}
bke::CurvesGeometry dst_curves = geometry::resample_to_count(
drawing->strokes(),
bke::GreasePencilLayerFieldContext(*grease_pencil, AttrDomain::Curve, layer_index),
fn::Field<bool>(true),
count,
resample_attributes);
pointcloud_by_layer[layer_index] = curves_to_points(
dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
}
layer_pointclouds_to_instances(pointcloud_by_layer, attribute_filter, geometry);
}
geometry.keep_only({bke::GeometryComponent::Type::PointCloud,
bke::GeometryComponent::Type::Instance,
bke::GeometryComponent::Type::Edit});
});
break;
}
case GEO_NODE_CURVE_RESAMPLE_LENGTH: {
const Field<float> length = params.extract_input<Field<float>>("Length"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry) {
if (const Curves *src_curves_id = geometry.get_curves()) {
bke::CurvesGeometry dst_curves = geometry::resample_to_length(
src_curves_id->geometry.wrap(),
bke::CurvesFieldContext(*src_curves_id, AttrDomain::Curve),
fn::Field<bool>(true),
length,
resample_attributes);
PointCloud *pointcloud = curves_to_points(
dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
geometry.replace_pointcloud(pointcloud);
}
if (const GreasePencil *grease_pencil = geometry.get_grease_pencil()) {
Array<PointCloud *> pointcloud_by_layer(grease_pencil->layers().size(), nullptr);
for (const int layer_index : grease_pencil->layers().index_range()) {
const bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(
grease_pencil->layer(layer_index));
if (drawing == nullptr) {
continue;
}
bke::CurvesGeometry dst_curves = geometry::resample_to_length(
drawing->strokes(),
bke::GreasePencilLayerFieldContext(*grease_pencil, AttrDomain::Curve, layer_index),
fn::Field<bool>(true),
length,
resample_attributes);
pointcloud_by_layer[layer_index] = curves_to_points(
dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
}
layer_pointclouds_to_instances(pointcloud_by_layer, attribute_filter, geometry);
}
geometry.keep_only({bke::GeometryComponent::Type::PointCloud,
bke::GeometryComponent::Type::Instance,
bke::GeometryComponent::Type::Edit});
});
break;
}
case GEO_NODE_CURVE_RESAMPLE_EVALUATED: {
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry) {
if (const Curves *src_curves_id = geometry.get_curves()) {
bke::CurvesGeometry dst_curves = geometry::resample_to_evaluated(
src_curves_id->geometry.wrap(),
bke::CurvesFieldContext(*src_curves_id, AttrDomain::Curve),
fn::Field<bool>(true),
resample_attributes);
PointCloud *pointcloud = curves_to_points(
dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
geometry.replace_pointcloud(pointcloud);
geometry.replace_curves(nullptr);
}
if (const GreasePencil *grease_pencil = geometry.get_grease_pencil()) {
Array<PointCloud *> pointcloud_by_layer(grease_pencil->layers().size(), nullptr);
for (const int layer_index : grease_pencil->layers().index_range()) {
const bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(
grease_pencil->layer(layer_index));
if (drawing == nullptr) {
continue;
}
bke::CurvesGeometry dst_curves = geometry::resample_to_evaluated(
drawing->strokes(),
bke::GreasePencilLayerFieldContext(*grease_pencil, AttrDomain::Curve, layer_index),
fn::Field<bool>(true),
resample_attributes);
pointcloud_by_layer[layer_index] = curves_to_points(
dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
}
layer_pointclouds_to_instances(pointcloud_by_layer, attribute_filter, geometry);
}
geometry.keep_only({bke::GeometryComponent::Type::PointCloud,
bke::GeometryComponent::Type::Instance,
bke::GeometryComponent::Type::Edit});
});
break;
}
}
params.set_output("Points"_ustr, std::move(geometry_set));
}
static void node_rna(StructRNA *srna)
{
static EnumPropertyItem mode_items[] = {
{GEO_NODE_CURVE_RESAMPLE_EVALUATED,
"EVALUATED",
0,
"Evaluated",
"Create points from the curve's evaluated points, based on the resolution attribute for "
"NURBS and Bézier splines"},
{GEO_NODE_CURVE_RESAMPLE_COUNT,
"COUNT",
0,
"Count",
"Sample each spline by evenly distributing the specified number of points"},
{GEO_NODE_CURVE_RESAMPLE_LENGTH,
"LENGTH",
0,
"Length",
"Sample each spline by splitting it into segments with the specified length"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"mode",
"Mode",
"How to generate points from the input curve",
mode_items,
NOD_storage_enum_accessors(mode),
GEO_NODE_CURVE_RESAMPLE_COUNT);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCurveToPoints"_ustr, GEO_NODE_CURVE_TO_POINTS);
ntype.ui_name = "Curve to Points";
ntype.ui_description = "Generate a point cloud by sampling positions along curves";
ntype.enum_name_legacy = "CURVE_TO_POINTS";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
bke::node_type_storage(
ntype, "NodeGeometryCurveToPoints", node_free_standard_storage, node_copy_standard_storage);
ntype.initfunc = node_init;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_to_points_cc

View File

@@ -0,0 +1,118 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_topology_curve_of_point_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Int>("Point Index"_ustr)
.default_input_type(NODE_DEFAULT_INPUT_INDEX_FIELD)
.description("The control point to retrieve data from")
.structure_type(StructureType::Field);
b.add_output<decl::Int>("Curve Index"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("The curve the control point is part of");
b.add_output<decl::Int>("Index in Curve"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("How far along the control point is along its curve");
}
class CurveOfPointInput final : public bke::CurvesFieldInput {
public:
CurveOfPointInput() : bke::CurvesFieldInput(CPPType::get<int>(), "Point Curve Index") {}
GVArray get_varray_for_context(const bke::CurvesGeometry &curves,
const AttrDomain domain,
const IndexMask & /*mask*/) const final
{
if (domain != AttrDomain::Point) {
return {};
}
return VArray<int>::from_container(curves.point_to_curve_map());
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep & /*deep_hash_cache*/) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
}
std::optional<AttrDomain> preferred_domain(const bke::CurvesGeometry & /*curves*/) const final
{
return AttrDomain::Point;
}
};
class PointIndexInCurveInput final : public bke::CurvesFieldInput {
public:
PointIndexInCurveInput() : bke::CurvesFieldInput(CPPType::get<int>(), "Point Index in Curve") {}
GVArray get_varray_for_context(const bke::CurvesGeometry &curves,
const AttrDomain domain,
const IndexMask & /*mask*/) const final
{
if (domain != AttrDomain::Point) {
return {};
}
const Span<int> offsets = curves.offsets();
Array<int> point_to_curve_map = curves.point_to_curve_map();
return VArray<int>::from_func(
curves.points_num(),
[offsets, point_to_curve_map = std::move(point_to_curve_map)](const int point_i) {
const int curve_i = point_to_curve_map[point_i];
return point_i - offsets[curve_i];
});
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep & /*deep_hash_cache*/) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
}
std::optional<AttrDomain> preferred_domain(const bke::CurvesGeometry & /*curves*/) const override
{
return AttrDomain::Point;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
const Field<int> point_index = params.extract_input<Field<int>>("Point Index"_ustr);
if (params.output_is_required("Curve Index"_ustr)) {
params.set_output(
"Curve Index"_ustr,
Field<int>::from_input<bke::EvaluateAtIndexInput>(
point_index, Field<int>::from_input<CurveOfPointInput>(), AttrDomain::Point));
}
if (params.output_is_required("Index in Curve"_ustr)) {
params.set_output(
"Index in Curve"_ustr,
Field<int>::from_input<bke::EvaluateAtIndexInput>(
point_index, Field<int>::from_input<PointIndexInCurveInput>(), AttrDomain::Point));
}
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeCurveOfPoint"_ustr, GEO_NODE_CURVE_TOPOLOGY_CURVE_OF_POINT);
ntype.ui_name = "Curve of Point";
ntype.ui_description = "Retrieve the curve a control point is part of";
ntype.enum_name_legacy = "CURVE_OF_POINT";
ntype.nclass = NODE_CLASS_INPUT;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_topology_curve_of_point_cc

View File

@@ -0,0 +1,226 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "BLI_array_utils.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_topology_points_of_curve_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Int>("Curve Index"_ustr)
.default_input_type(NODE_DEFAULT_INPUT_INDEX_FIELD)
.description("The curve to retrieve data from. Defaults to the curve from the context")
.structure_type(StructureType::Field);
b.add_input<decl::Float>("Weights"_ustr)
.structure_type(StructureType::Field)
.hide_value()
.description("Values used to sort the curve's points. Uses indices by default");
b.add_input<decl::Int>("Sort Index"_ustr)
.structure_type(StructureType::Field)
.description("Which of the sorted points to output. Negative indexing is supported");
b.add_output<decl::Int>("Point Index"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("A point of the curve, chosen by the sort index");
b.add_output<decl::Int>("Total"_ustr)
.structure_type(StructureType::Field)
.propagate_references({0})
.description("The number of points in the curve");
}
/**
* The node is often used to retrieve the root point of the curve. If the curve indices are in
* order, the sort weights have no effect, and the sort index is the first point, then we can just
* return the curve offsets as a span directly.
*/
static bool use_start_point_special_case(const Field<int> &curve_index,
const Field<int> &sort_index,
const Field<float> &sort_weights)
{
if (!curve_index.get_input_if<fn::IndexFieldInput>()) {
return false;
}
if (sort_index.depends_on_input() || sort_weights.depends_on_input()) {
return false;
}
return fn::evaluate_constant_field(sort_index) == 0;
}
class PointsOfCurveInput final : public bke::GeometryFieldInput {
const Field<int> curve_index_;
const Field<int> sort_index_;
const Field<float> sort_weight_;
public:
PointsOfCurveInput(Field<int> curve_index, Field<int> sort_index, Field<float> sort_weight)
: bke::GeometryFieldInput(CPPType::get<int>(), "Point of Curve"),
curve_index_(std::move(curve_index)),
sort_index_(std::move(sort_index)),
sort_weight_(std::move(sort_weight))
{
}
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
const IndexMask &mask) const final
{
const bke::CurvesGeometry *curves_ptr = context.curves_or_strokes();
if (!curves_ptr) {
return {};
}
const bke::CurvesGeometry &curves = *curves_ptr;
const OffsetIndices points_by_curve = curves.points_by_curve();
if (context.domain() == AttrDomain::Curve) {
if (use_start_point_special_case(curve_index_, sort_index_, sort_weight_)) {
return VArray<int>::from_span(points_by_curve.data());
}
}
fn::FieldEvaluator evaluator{context, &mask};
evaluator.add(curve_index_);
evaluator.add(sort_index_);
evaluator.evaluate();
const VArray<int> curve_indices = evaluator.get_evaluated<int>(0);
const VArray<int> indices_in_sort = evaluator.get_evaluated<int>(1);
const bke::GeometryFieldContext point_context{context, AttrDomain::Point};
fn::FieldEvaluator point_evaluator{point_context, curves.points_num()};
point_evaluator.add(sort_weight_);
point_evaluator.evaluate();
const VArray<float> all_sort_weights = point_evaluator.get_evaluated<float>(0);
const bool use_sorting = !all_sort_weights.is_single();
Array<int> point_of_curve(mask.min_array_size());
mask.foreach_segment(
[&](const IndexMaskSegment segment) {
/* Reuse arrays to avoid allocation. */
Array<float> sort_weights;
Array<int> sort_indices;
for (const int selection_i : segment) {
const int curve_i = curve_indices[selection_i];
const int index_in_sort = indices_in_sort[selection_i];
if (!curves.curves_range().contains(curve_i)) {
point_of_curve[selection_i] = 0;
continue;
}
const IndexRange points = points_by_curve[curve_i];
const int index_in_sort_wrapped = mod_i(index_in_sort, points.size());
if (use_sorting) {
/* Retrieve the weights for each point. */
sort_weights.reinitialize(points.size());
all_sort_weights.materialize_compressed(IndexMask(points),
sort_weights.as_mutable_span());
/* Sort a separate array of compressed indices corresponding to the compressed
* weights. This allows using `materialize_compressed` to avoid virtual function call
* overhead when accessing values in the sort weights. However, it means a separate
* array of indices within the compressed array is necessary for sorting. */
sort_indices.reinitialize(points.size());
array_utils::fill_index_range<int>(sort_indices);
std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
return sort_weights[a] < sort_weights[b];
});
point_of_curve[selection_i] = points[sort_indices[index_in_sort_wrapped]];
}
else {
point_of_curve[selection_i] = points[index_in_sort_wrapped];
}
}
},
exec_mode::grain_size(256));
return VArray<int>::from_container(std::move(point_of_curve));
}
void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const override
{
fn(curve_index_);
fn(sort_index_);
fn(sort_weight_);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(curve_index_));
hash.add(deep_hash_cache.ensure(sort_index_));
hash.add(deep_hash_cache.ensure(sort_weight_));
}
std::optional<AttrDomain> preferred_domain(const GeometryComponent & /*component*/) const final
{
return AttrDomain::Curve;
}
};
class CurvePointCountInput final : public bke::CurvesFieldInput {
public:
CurvePointCountInput() : bke::CurvesFieldInput(CPPType::get<int>(), "Curve Point Count") {}
GVArray get_varray_for_context(const bke::CurvesGeometry &curves,
const AttrDomain domain,
const IndexMask & /*mask*/) const final
{
if (domain != AttrDomain::Curve) {
return {};
}
const OffsetIndices points_by_curve = curves.points_by_curve();
return VArray<int>::from_func(curves.curves_num(), [points_by_curve](const int64_t curve_i) {
return points_by_curve[curve_i].size();
});
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep & /*deep_hash_cache*/) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
}
std::optional<AttrDomain> preferred_domain(const bke::CurvesGeometry & /*curves*/) const final
{
return AttrDomain::Curve;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
const Field<int> curve_index = params.extract_input<Field<int>>("Curve Index"_ustr);
if (params.output_is_required("Total"_ustr)) {
params.set_output(
"Total"_ustr,
Field<int>::from_input<bke::EvaluateAtIndexInput>(
curve_index, Field<int>::from_input<CurvePointCountInput>(), AttrDomain::Curve));
}
if (params.output_is_required("Point Index"_ustr)) {
params.set_output("Point Index"_ustr,
Field<int>::from_input<PointsOfCurveInput>(
curve_index,
params.extract_input<Field<int>>("Sort Index"_ustr),
params.extract_input<Field<float>>("Weights"_ustr)));
}
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodePointsOfCurve"_ustr, GEO_NODE_CURVE_TOPOLOGY_POINTS_OF_CURVE);
ntype.ui_name = "Points of Curve";
ntype.ui_description = "Retrieve a point index within a curve";
ntype.enum_name_legacy = "POINTS_OF_CURVE";
ntype.nclass = NODE_CLASS_INPUT;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_topology_points_of_curve_cc

View File

@@ -0,0 +1,288 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_socket_search_link.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_trim_curves.hh"
#include "NOD_rna_define.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_trim_cc {
NODE_STORAGE_FUNCS(NodeGeometryCurveTrim)
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
b.add_input<decl::Geometry>("Curve"_ustr)
.supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil})
.description("Curves to shorten");
b.add_output<decl::Geometry>("Curve"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field();
auto &start_fac = b.add_input<decl::Float>("Start"_ustr)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR;
})
.evaluated_geometry_field();
auto &end_fac = b.add_input<decl::Float>("End"_ustr)
.min(0.0f)
.max(1.0f)
.default_value(1.0f)
.subtype(PROP_FACTOR)
.make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR;
})
.evaluated_geometry_field();
auto &start_len = b.add_input<decl::Float>("Start"_ustr, "Start_001"_ustr)
.min(0.0f)
.subtype(PROP_DISTANCE)
.make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH;
})
.evaluated_geometry_field();
auto &end_len = b.add_input<decl::Float>("End"_ustr, "End_001"_ustr)
.min(0.0f)
.default_value(1.0f)
.subtype(PROP_DISTANCE)
.make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH;
})
.evaluated_geometry_field();
const bNode *node = b.node_or_null();
if (node != nullptr) {
const NodeGeometryCurveTrim &storage = node_storage(*node);
const GeometryNodeCurveSampleMode mode = GeometryNodeCurveSampleMode(storage.mode);
start_fac.available(mode == GEO_NODE_CURVE_SAMPLE_FACTOR);
end_fac.available(mode == GEO_NODE_CURVE_SAMPLE_FACTOR);
start_len.available(mode == GEO_NODE_CURVE_SAMPLE_LENGTH);
end_len.available(mode == GEO_NODE_CURVE_SAMPLE_LENGTH);
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurveTrim *data = MEM_new<NodeGeometryCurveTrim>(__func__);
data->mode = GEO_NODE_CURVE_SAMPLE_FACTOR;
node->storage = data;
}
class SocketSearchOp {
public:
UString socket_name;
GeometryNodeCurveSampleMode mode;
void operator()(LinkSearchOpParams &params)
{
bNode &node = params.add_node("GeometryNodeTrimCurve"_ustr);
node_storage(node).mode = mode;
params.update_and_connect_available_socket(node, socket_name);
}
};
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const NodeDeclaration &declaration = *params.node_type().static_declaration;
search_link_ops_for_declarations(params, declaration.outputs);
search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(1));
if (params.in_out() == SOCK_IN) {
if (params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_FLOAT)) {
params.add_item(IFACE_("Start (Factor)"),
SocketSearchOp{"Start"_ustr, GEO_NODE_CURVE_SAMPLE_FACTOR});
params.add_item(IFACE_("End (Factor)"),
SocketSearchOp{"End"_ustr, GEO_NODE_CURVE_SAMPLE_FACTOR});
params.add_item(IFACE_("Start (Length)"),
SocketSearchOp{"Start"_ustr, GEO_NODE_CURVE_SAMPLE_LENGTH});
params.add_item(IFACE_("End (Length)"),
SocketSearchOp{"End"_ustr, GEO_NODE_CURVE_SAMPLE_LENGTH});
}
}
}
static bool trim_curves(const bke::CurvesGeometry &src_curves,
const GeometryNodeCurveSampleMode mode,
const fn::FieldContext &field_context,
Field<bool> &selection_field,
Field<float> &start_field,
Field<float> &end_field,
const AttributeFilter &attribute_filter,
bke::CurvesGeometry &dst_curves)
{
if (src_curves.is_empty()) {
return false;
}
fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()};
evaluator.add(selection_field);
evaluator.add(start_field);
evaluator.add(end_field);
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_as_mask(0);
const VArray<float> starts = evaluator.get_evaluated<float>(1);
const VArray<float> ends = evaluator.get_evaluated<float>(2);
if (selection.is_empty()) {
return false;
}
dst_curves = geometry::trim_curves(src_curves, selection, starts, ends, mode, attribute_filter);
return true;
}
static void geometry_set_curve_trim(GeometrySet &geometry_set,
const GeometryNodeCurveSampleMode mode,
Field<bool> &selection_field,
Field<float> &start_field,
Field<float> &end_field,
const AttributeFilter &attribute_filter)
{
if (geometry_set.has_curves()) {
const Curves &src_curves_id = *geometry_set.get_curves();
const bke::CurvesGeometry &src_curves = src_curves_id.geometry.wrap();
const bke::CurvesFieldContext field_context{src_curves_id, AttrDomain::Curve};
bke::CurvesGeometry dst_curves;
if (trim_curves(src_curves,
mode,
field_context,
selection_field,
start_field,
end_field,
attribute_filter,
dst_curves))
{
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
bke::curves_copy_parameters(src_curves_id, *dst_curves_id);
geometry_set.replace_curves(dst_curves_id);
}
}
if (geometry_set.has_grease_pencil()) {
using namespace bke::greasepencil;
GreasePencil &grease_pencil = *geometry_set.get_grease_pencil_for_write();
for (const int layer_index : grease_pencil.layers().index_range()) {
Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &src_curves = drawing->strokes();
const bke::GreasePencilLayerFieldContext field_context{
grease_pencil, AttrDomain::Curve, layer_index};
bke::CurvesGeometry dst_curves;
if (trim_curves(src_curves,
mode,
field_context,
selection_field,
start_field,
end_field,
attribute_filter,
dst_curves))
{
drawing->strokes_for_write() = std::move(dst_curves);
drawing->tag_topology_changed();
}
}
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryCurveTrim &storage = node_storage(params.node());
const GeometryNodeCurveSampleMode mode = GeometryNodeCurveSampleMode(storage.mode);
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"_ustr);
GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set);
const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Curve"_ustr);
Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"_ustr);
if (mode == GEO_NODE_CURVE_SAMPLE_FACTOR) {
Field<float> start_field = params.extract_input<Field<float>>("Start"_ustr);
Field<float> end_field = params.extract_input<Field<float>>("End"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
geometry_set_curve_trim(
geometry_set, mode, selection_field, start_field, end_field, attribute_filter);
});
}
else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) {
Field<float> start_field = params.extract_input<Field<float>>("Start_001"_ustr);
Field<float> end_field = params.extract_input<Field<float>>("End_001"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
geometry_set_curve_trim(
geometry_set, mode, selection_field, start_field, end_field, attribute_filter);
});
}
params.set_output("Curve"_ustr, std::move(geometry_set));
}
static void node_rna(StructRNA *srna)
{
static EnumPropertyItem mode_items[] = {
{GEO_NODE_CURVE_SAMPLE_FACTOR,
"FACTOR",
0,
"Factor",
"Find the endpoint positions using a factor of each spline's length"},
{GEO_NODE_CURVE_RESAMPLE_LENGTH,
"LENGTH",
0,
"Length",
"Find the endpoint positions using a length from the start of each spline"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"mode",
"Mode",
"How to find endpoint positions for the trimmed spline",
mode_items,
NOD_storage_enum_accessors(mode));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeTrimCurve"_ustr, GEO_NODE_TRIM_CURVE);
ntype.ui_name = "Trim Curve";
ntype.ui_description = "Shorten curves by removing portions at the start or end";
ntype.enum_name_legacy = "TRIM_CURVE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
ntype.declare = node_declare;
bke::node_type_storage(
ntype, "NodeGeometryCurveTrim", node_free_standard_storage, node_copy_standard_storage);
ntype.initfunc = node_init;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curve_trim_cc

View File

@@ -0,0 +1,238 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curves_to_grease_pencil_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Curves"_ustr).description("Either plain curves or curve instances");
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field()
.description("Either a curve or instance selection");
b.add_input<decl::Bool>("Instances as Layers"_ustr)
.default_value(true)
.description("Create a separate layer for each instance");
b.add_output<decl::Geometry>("Grease Pencil"_ustr).propagate_all_geometry();
}
static GreasePencil *curves_to_grease_pencil_with_one_layer(
const Curves &curves_id,
const Field<bool> &selection_field,
const StringRefNull layer_name,
const AttributeFilter &attribute_filter)
{
bke::CurvesGeometry curves = curves_id.geometry.wrap();
const bke::CurvesFieldContext field_context{curves_id, AttrDomain::Curve};
FieldEvaluator evaluator{field_context, curves.curves_num()};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask curves_selection = evaluator.get_evaluated_selection_as_mask();
IndexMaskMemory memory;
const IndexMask curves_to_delete = curves_selection.complement(curves.curves_range(), memory);
curves.remove_curves(curves_to_delete, attribute_filter);
GreasePencil *grease_pencil = BKE_grease_pencil_new_nomain();
grease_pencil->add_layers_with_empty_drawings_for_eval(1);
bke::greasepencil::Layer &layer = grease_pencil->layer(0);
layer.set_name(layer_name);
bke::greasepencil::Drawing &drawing = *grease_pencil->get_eval_drawing(layer);
drawing.strokes_for_write() = std::move(curves);
/* Transfer materials. */
const int materials_num = curves_id.totcol;
grease_pencil->material_array_num = materials_num;
grease_pencil->material_array = MEM_new_array_zeroed<Material *>(materials_num, __func__);
initialized_copy_n(curves_id.mat, materials_num, grease_pencil->material_array);
return grease_pencil;
}
static GreasePencil *curve_instances_to_grease_pencil_layers(
const bke::Instances &instances,
const Field<bool> &selection_field,
const AttributeFilter &attribute_filter)
{
const Span<int> reference_handles = instances.reference_handles();
const Span<bke::InstanceReference> references = instances.references();
const Span<float4x4> transforms = instances.transforms();
const int instances_num = instances.instances_num();
if (instances_num == 0) {
return nullptr;
}
const bke::InstancesFieldContext field_context{instances};
FieldEvaluator evaluator{field_context, instances_num};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask instance_selection = evaluator.get_evaluated_selection_as_mask();
const int layer_num = instance_selection.size();
if (layer_num == 0) {
return nullptr;
}
GreasePencil *grease_pencil = BKE_grease_pencil_new_nomain();
VectorSet<Material *> all_materials;
grease_pencil->add_layers_with_empty_drawings_for_eval(layer_num);
instance_selection.foreach_index([&](const int instance_i, const int layer_i) {
const bke::InstanceReference &reference = references[reference_handles[instance_i]];
bke::greasepencil::Layer &layer = grease_pencil->layer(layer_i);
bke::greasepencil::Drawing &drawing = *grease_pencil->get_eval_drawing(layer);
layer.set_name(reference.name());
layer.set_local_transform(transforms[instance_i]);
GeometrySet instance_geometry;
reference.to_geometry_set(instance_geometry);
const Curves *instance_curves = instance_geometry.get_curves();
if (!instance_curves) {
return;
}
bke::CurvesGeometry &strokes = drawing.strokes_for_write();
strokes = instance_curves->geometry.wrap();
Vector<int> new_material_indices;
for (Material *material : Span{instance_curves->mat, instance_curves->totcol}) {
new_material_indices.append(all_materials.index_of_or_add(material));
}
/* Remap material indices. */
bke::SpanAttributeWriter<int> material_indices =
strokes.attributes_for_write().lookup_or_add_for_write_span<int>("material_index",
bke::AttrDomain::Curve);
for (int &material_index : material_indices.span) {
if (material_index >= 0 && material_index < new_material_indices.size()) {
material_index = new_material_indices[material_index];
}
}
material_indices.finish();
});
grease_pencil->material_array_num = all_materials.size();
grease_pencil->material_array = MEM_new_array_zeroed<Material *>(all_materials.size(), __func__);
initialized_copy_n(all_materials.data(), all_materials.size(), grease_pencil->material_array);
const bke::AttributeAccessor instances_attributes = instances.attributes();
bke::MutableAttributeAccessor grease_pencil_attributes = grease_pencil->attributes_for_write();
instances_attributes.foreach_attribute([&](const AttributeIter &iter) {
if (iter.is_builtin && !grease_pencil_attributes.is_builtin(iter.name)) {
return;
}
if (iter.data_type == bke::AttrType::String) {
return;
}
if (ELEM(iter.name, "opacity")) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const GAttributeReader src_attribute = iter.get();
if (instance_selection.size() == instances_num && src_attribute.varray.is_span() &&
src_attribute.sharing_info)
{
/* Try reusing existing attribute array. */
grease_pencil_attributes.add(
iter.name,
AttrDomain::Layer,
iter.data_type,
bke::AttributeInitShared{src_attribute.varray.get_internal_span().data(),
*src_attribute.sharing_info});
return;
}
if (!grease_pencil_attributes.add(
iter.name, AttrDomain::Layer, iter.data_type, bke::AttributeInitConstruct()))
{
return;
}
bke::GSpanAttributeWriter dst_attribute = grease_pencil_attributes.lookup_for_write_span(
iter.name);
array_utils::gather(src_attribute.varray, instance_selection, dst_attribute.span);
dst_attribute.finish();
});
{
/* Manually propagate "opacity" data, because it's not a layer attribute on grease pencil
* yet. Default to a full opacity of 1. */
const VArray<float> opacities = *instances_attributes.lookup_or_default<float>(
"opacity", AttrDomain::Instance, 1.0f);
instance_selection.foreach_index([&](const int instance_i, const int layer_i) {
grease_pencil->layer(layer_i).opacity = opacities[instance_i];
});
}
return grease_pencil;
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet curves_geometry = params.extract_input<GeometrySet>("Curves"_ustr);
const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"_ustr);
const bool instances_as_layers = params.extract_input<bool>("Instances as Layers"_ustr);
const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Grease Pencil"_ustr);
GreasePencil *grease_pencil = nullptr;
if (instances_as_layers) {
if (curves_geometry.has_curves()) {
params.error_message_add(NodeWarningType::Info, TIP_("Non-instance curves are ignored"));
}
const bke::Instances *instances = curves_geometry.get_instances();
if (!instances) {
params.set_default_remaining_outputs();
return;
}
grease_pencil = curve_instances_to_grease_pencil_layers(
*instances, selection_field, attribute_filter);
}
else {
if (curves_geometry.has_instances()) {
params.error_message_add(NodeWarningType::Info, TIP_("Instances are ignored"));
}
const Curves *curves_id = curves_geometry.get_curves();
if (!curves_id) {
params.set_default_remaining_outputs();
return;
}
grease_pencil = curves_to_grease_pencil_with_one_layer(
*curves_id, selection_field, curves_geometry.name(), attribute_filter);
}
GeometrySet grease_pencil_geometry = GeometrySet::from_grease_pencil(grease_pencil);
grease_pencil_geometry.set_name(curves_geometry.name());
grease_pencil_geometry.copy_bundle_from(curves_geometry);
params.set_output("Grease Pencil"_ustr, std::move(grease_pencil_geometry));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeCurvesToGreasePencil"_ustr, GEO_NODE_CURVES_TO_GREASE_PENCIL);
ntype.ui_name = "Curves to Grease Pencil";
ntype.ui_description = "Convert the curves in each top-level instance into Grease Pencil layer";
ntype.enum_name_legacy = "CURVES_TO_GREASE_PENCIL";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
ntype.default_width = bke::NodeWidth::_180;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curves_to_grease_pencil_cc

View File

@@ -0,0 +1,418 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_editmesh.hh"
#include "BKE_lib_id.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_wrapper.hh"
#include "BKE_modifier.hh"
#include "BLI_math_matrix.hh"
#include "BLI_task.hh"
#include "GEO_reverse_uv_sampler.hh"
#include "DEG_depsgraph_query.hh"
#include "node_geometry_util.hh"
#include <fmt/format.h>
namespace blender::nodes::node_geo_deform_curves_on_surface_cc {
using bke::CurvesGeometry;
using bke::attribute_math::mix3;
using geometry::ReverseUVSampler;
NODE_STORAGE_FUNCS(NodeGeometryCurveTrim)
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Geometry>("Curves"_ustr)
.supported_type(GeometryComponent::Type::Curve)
.description("Curves to deform");
b.add_output<decl::Geometry>("Curves"_ustr).propagate_all_geometry().align_with_previous();
}
static void deform_curves(const CurvesGeometry &curves,
const Mesh &surface_mesh_old,
const Mesh &surface_mesh_new,
const Span<float2> curve_attachment_uvs,
const ReverseUVSampler &reverse_uv_sampler_old,
const ReverseUVSampler &reverse_uv_sampler_new,
const Span<float3> corner_normals_old,
const Span<float3> corner_normals_new,
const Span<float3> rest_positions,
const float4x4 &surface_to_curves,
MutableSpan<float3> r_positions,
MutableSpan<float3x3> r_rotations,
std::atomic<int> &r_invalid_uv_count)
{
/* Find attachment points on old and new mesh. */
const int curves_num = curves.curves_num();
Array<ReverseUVSampler::Result> surface_samples_old(curves_num);
Array<ReverseUVSampler::Result> surface_samples_new(curves_num);
threading::parallel_invoke(
1024 < curves_num,
[&]() { reverse_uv_sampler_old.sample_many(curve_attachment_uvs, surface_samples_old); },
[&]() { reverse_uv_sampler_new.sample_many(curve_attachment_uvs, surface_samples_new); });
const float4x4 curves_to_surface = math::invert(surface_to_curves);
const Span<float3> surface_positions_old = surface_mesh_old.vert_positions();
const Span<int> surface_corner_verts_old = surface_mesh_old.corner_verts();
const Span<int3> surface_corner_tris_old = surface_mesh_old.corner_tris();
const Span<float3> surface_positions_new = surface_mesh_new.vert_positions();
const Span<int> surface_corner_verts_new = surface_mesh_new.corner_verts();
const Span<int3> surface_corner_tris_new = surface_mesh_new.corner_tris();
const OffsetIndices points_by_curve = curves.points_by_curve();
threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) {
for (const int curve_i : range) {
const ReverseUVSampler::Result &surface_sample_old = surface_samples_old[curve_i];
if (surface_sample_old.type != ReverseUVSampler::ResultType::Ok) {
r_invalid_uv_count++;
continue;
}
const ReverseUVSampler::Result &surface_sample_new = surface_samples_new[curve_i];
if (surface_sample_new.type != ReverseUVSampler::ResultType::Ok) {
r_invalid_uv_count++;
continue;
}
const int3 &tri_old = surface_corner_tris_old[surface_sample_old.tri_index];
const int3 &tri_new = surface_corner_tris_new[surface_sample_new.tri_index];
const float3 &bary_weights_old = surface_sample_old.bary_weights;
const float3 &bary_weights_new = surface_sample_new.bary_weights;
const int corner_0_old = tri_old[0];
const int corner_1_old = tri_old[1];
const int corner_2_old = tri_old[2];
const int corner_0_new = tri_new[0];
const int corner_1_new = tri_new[1];
const int corner_2_new = tri_new[2];
const int vert_0_old = surface_corner_verts_old[corner_0_old];
const int vert_1_old = surface_corner_verts_old[corner_1_old];
const int vert_2_old = surface_corner_verts_old[corner_2_old];
const int vert_0_new = surface_corner_verts_new[corner_0_new];
const int vert_1_new = surface_corner_verts_new[corner_1_new];
const int vert_2_new = surface_corner_verts_new[corner_2_new];
const float3 &normal_0_old = corner_normals_old[corner_0_old];
const float3 &normal_1_old = corner_normals_old[corner_1_old];
const float3 &normal_2_old = corner_normals_old[corner_2_old];
const float3 normal_old = math::normalize(
mix3(bary_weights_old, normal_0_old, normal_1_old, normal_2_old));
const float3 &normal_0_new = corner_normals_new[corner_0_new];
const float3 &normal_1_new = corner_normals_new[corner_1_new];
const float3 &normal_2_new = corner_normals_new[corner_2_new];
const float3 normal_new = math::normalize(
mix3(bary_weights_new, normal_0_new, normal_1_new, normal_2_new));
const float3 &pos_0_old = surface_positions_old[vert_0_old];
const float3 &pos_1_old = surface_positions_old[vert_1_old];
const float3 &pos_2_old = surface_positions_old[vert_2_old];
const float3 pos_old = mix3(bary_weights_old, pos_0_old, pos_1_old, pos_2_old);
const float3 &pos_0_new = surface_positions_new[vert_0_new];
const float3 &pos_1_new = surface_positions_new[vert_1_new];
const float3 &pos_2_new = surface_positions_new[vert_2_new];
const float3 pos_new = mix3(bary_weights_new, pos_0_new, pos_1_new, pos_2_new);
/* The translation is just the difference between the old and new position on the surface. */
const float3 translation = pos_new - pos_old;
const float3 &rest_pos_0 = rest_positions[vert_0_new];
const float3 &rest_pos_1 = rest_positions[vert_1_new];
/* The tangent reference direction is used to determine the rotation of the surface point
* around its normal axis. It's important that the old and new tangent reference are computed
* in a consistent way. If the surface has not been rotated, the old and new tangent
* reference have to have the same direction. For that reason, the old tangent reference is
* computed based on the rest position attribute instead of positions on the old mesh. This
* way the old and new tangent reference use the same topology.
*
* TODO: Figure out if this can be smoothly interpolated across the surface as well.
* Currently, this is a source of discontinuity in the deformation, because the vector
* changes instantly from one triangle to the next. */
const float3 tangent_reference_dir_old = rest_pos_1 - rest_pos_0;
const float3 tangent_reference_dir_new = pos_1_new - pos_0_new;
/* Compute first local tangent based on the (potentially smoothed) normal and the tangent
* reference. */
const float3 tangent_x_old = math::normalize(
math::cross(normal_old, tangent_reference_dir_old));
const float3 tangent_x_new = math::normalize(
math::cross(normal_new, tangent_reference_dir_new));
/* The second tangent defined by the normal and first tangent. */
const float3 tangent_y_old = math::normalize(math::cross(normal_old, tangent_x_old));
const float3 tangent_y_new = math::normalize(math::cross(normal_new, tangent_x_new));
/* Construct rotation matrix that encodes the orientation of the old surface position. */
float3x3 rotation_old(tangent_x_old, tangent_y_old, normal_old);
/* Construct rotation matrix that encodes the orientation of the new surface position. */
float3x3 rotation_new(tangent_x_new, tangent_y_new, normal_new);
/* Can use transpose instead of inverse because the matrix is orthonormal. In the case of
* zero-area triangles, the matrix would not be orthonormal, but in this case, none of this
* works anyway. */
const float3x3 rotation_old_inv = math::transpose(rotation_old);
/* Compute a rotation matrix that rotates points from the old to the new surface
* orientation. */
const float3x3 rotation = rotation_new * rotation_old_inv;
/* Construction transformation matrix for this surface position that includes rotation and
* translation. */
/* Subtract and add #pos_old, so that the rotation origin is the position on the surface. */
float4x4 surface_transform = math::from_origin_transform<float4x4>(float4x4(rotation),
pos_old);
surface_transform.location() += translation;
/* Change the basis of the transformation so to that it can be applied in the local space of
* the curves. */
const float4x4 curve_transform = surface_to_curves * surface_transform * curves_to_surface;
/* Actually transform all points. */
const IndexRange points = points_by_curve[curve_i];
for (const int point_i : points) {
const float3 old_point_pos = r_positions[point_i];
const float3 new_point_pos = math::transform_point(curve_transform, old_point_pos);
r_positions[point_i] = new_point_pos;
}
if (!r_rotations.is_empty()) {
for (const int point_i : points) {
r_rotations[point_i] = rotation * r_rotations[point_i];
}
}
}
});
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet curves_geometry = params.extract_input<GeometrySet>("Curves"_ustr);
Mesh *surface_mesh_orig = nullptr;
bool free_suface_mesh_orig = false;
BLI_SCOPED_DEFER([&]() {
if (free_suface_mesh_orig) {
BKE_id_free(nullptr, surface_mesh_orig);
}
});
auto pass_through_input = [&]() {
params.set_output("Curves"_ustr, std::move(curves_geometry));
};
const Object *self_ob_eval = params.self_object();
if (self_ob_eval == nullptr || self_ob_eval->type != OB_CURVES) {
pass_through_input();
params.error_message_add(NodeWarningType::Error, TIP_("Node only works for curves objects"));
return;
}
const Curves *self_curves_eval = id_cast<const Curves *>(self_ob_eval->data);
if (self_curves_eval->surface_uv_map == nullptr || self_curves_eval->surface_uv_map[0] == '\0') {
pass_through_input();
params.error_message_add(NodeWarningType::Error, TIP_("Surface UV map not defined"));
return;
}
/* Take surface information from self-object. */
Object *surface_ob_eval = self_curves_eval->surface;
const StringRefNull uv_map_name = self_curves_eval->surface_uv_map;
const StringRefNull rest_position_name = "rest_position";
if (!curves_geometry.has_curves()) {
pass_through_input();
return;
}
if (surface_ob_eval == nullptr || surface_ob_eval->type != OB_MESH) {
pass_through_input();
params.error_message_add(NodeWarningType::Error, TIP_("Curves not attached to a surface"));
return;
}
Object *surface_ob_orig = DEG_get_original(surface_ob_eval);
Mesh &surface_object_data = *id_cast<Mesh *>(surface_ob_orig->data);
if (BMEditMesh *em = surface_object_data.runtime->edit_mesh.get()) {
surface_mesh_orig = BKE_mesh_from_bmesh_for_eval_nomain(em->bm, nullptr, &surface_object_data);
free_suface_mesh_orig = true;
}
else {
surface_mesh_orig = &surface_object_data;
}
Mesh *surface_mesh_eval = BKE_modifier_get_evaluated_mesh_from_evaluated_object(surface_ob_eval);
if (surface_mesh_eval == nullptr) {
pass_through_input();
params.error_message_add(NodeWarningType::Error, TIP_("Surface has no mesh"));
return;
}
BKE_mesh_wrapper_ensure_mdata(surface_mesh_eval);
const AttributeAccessor mesh_attributes_eval = surface_mesh_eval->attributes();
const AttributeAccessor mesh_attributes_orig = surface_mesh_orig->attributes();
Curves &curves_id = *curves_geometry.get_curves_for_write();
CurvesGeometry &curves = curves_id.geometry.wrap();
if (!mesh_attributes_eval.contains(uv_map_name)) {
pass_through_input();
const std::string message = fmt::format(
fmt::runtime(TIP_("Evaluated surface missing UV map: \"{}\"")), uv_map_name);
params.error_message_add(NodeWarningType::Error, message);
return;
}
if (!mesh_attributes_orig.contains(uv_map_name)) {
pass_through_input();
const std::string message = fmt::format(
fmt::runtime(TIP_("Original surface missing UV map: \"{}\"")), uv_map_name);
params.error_message_add(NodeWarningType::Error, message);
return;
}
if (!mesh_attributes_eval.contains(rest_position_name)) {
pass_through_input();
params.error_message_add(NodeWarningType::Error,
TIP_("Evaluated surface missing attribute: \"rest_position\""));
return;
}
if (!curves.surface_uv_coords() && curves.curves_num() > 0) {
pass_through_input();
params.error_message_add(NodeWarningType::Error,
TIP_("Curves are not attached to any UV map"));
return;
}
const VArraySpan uv_map_orig = *mesh_attributes_orig.lookup<float2>(uv_map_name,
AttrDomain::Corner);
const VArraySpan uv_map_eval = *mesh_attributes_eval.lookup<float2>(uv_map_name,
AttrDomain::Corner);
const VArraySpan rest_positions = *mesh_attributes_eval.lookup<float3>(rest_position_name,
AttrDomain::Point);
const VArraySpan surface_uv_coords = *curves.attributes().lookup_or_default<float2>(
"surface_uv_coordinate", AttrDomain::Curve, float2(0));
const Span<int3> corner_tris_orig = surface_mesh_orig->corner_tris();
const Span<int3> corner_tris_eval = surface_mesh_eval->corner_tris();
const ReverseUVSampler reverse_uv_sampler_orig{uv_map_orig, corner_tris_orig};
const ReverseUVSampler reverse_uv_sampler_eval{uv_map_eval, corner_tris_eval};
/* Retrieve face corner normals from each mesh. It's necessary to use face corner normals
* because face normals or vertex normals may lose information (custom normals, auto smooth) in
* some cases. */
const Span<float3> corner_normals_orig = surface_mesh_orig->corner_normals();
const Span<float3> corner_normals_eval = surface_mesh_eval->corner_normals();
std::atomic<int> invalid_uv_count = 0;
const bke::CurvesSurfaceTransforms transforms{*self_ob_eval, surface_ob_eval};
bke::CurvesEditHints *edit_hints = curves_geometry.get_curve_edit_hints_for_write();
MutableSpan<float3> edit_hint_positions;
MutableSpan<float3x3> edit_hint_rotations;
if (edit_hints != nullptr) {
if (const std::optional<MutableSpan<float3>> positions = edit_hints->positions_for_write()) {
edit_hint_positions = *positions;
}
if (!edit_hints->deform_mats.has_value()) {
edit_hints->deform_mats.emplace(edit_hints->curves_id_orig.geometry.point_num,
float3x3::identity());
edit_hints->deform_mats->fill(float3x3::identity());
}
edit_hint_rotations = *edit_hints->deform_mats;
}
if (edit_hint_positions.is_empty()) {
deform_curves(curves,
*surface_mesh_orig,
*surface_mesh_eval,
surface_uv_coords,
reverse_uv_sampler_orig,
reverse_uv_sampler_eval,
corner_normals_orig,
corner_normals_eval,
rest_positions,
transforms.surface_to_curves,
curves.positions_for_write(),
edit_hint_rotations,
invalid_uv_count);
}
else {
/* First deform the actual curves in the input geometry. */
deform_curves(curves,
*surface_mesh_orig,
*surface_mesh_eval,
surface_uv_coords,
reverse_uv_sampler_orig,
reverse_uv_sampler_eval,
corner_normals_orig,
corner_normals_eval,
rest_positions,
transforms.surface_to_curves,
curves.positions_for_write(),
{},
invalid_uv_count);
/* Then also deform edit curve information for use in sculpt mode. */
const CurvesGeometry &curves_orig = edit_hints->curves_id_orig.geometry.wrap();
const VArraySpan<float2> surface_uv_coords_orig = *curves_orig.attributes().lookup_or_default(
"surface_uv_coordinate", AttrDomain::Curve, float2(0));
if (!surface_uv_coords_orig.is_empty()) {
deform_curves(curves_orig,
*surface_mesh_orig,
*surface_mesh_eval,
surface_uv_coords_orig,
reverse_uv_sampler_orig,
reverse_uv_sampler_eval,
corner_normals_orig,
corner_normals_eval,
rest_positions,
transforms.surface_to_curves,
edit_hint_positions,
edit_hint_rotations,
invalid_uv_count);
}
}
curves.tag_positions_changed();
if (invalid_uv_count) {
const std::string message = fmt::format(fmt::runtime(TIP_("Invalid surface UVs on {} curves")),
invalid_uv_count.load());
params.error_message_add(NodeWarningType::Warning, message);
}
params.set_output("Curves"_ustr, curves_geometry);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeDeformCurvesOnSurface"_ustr, GEO_NODE_DEFORM_CURVES_ON_SURFACE);
ntype.ui_name = "Deform Curves on Surface";
ntype.ui_description =
"Translate and rotate curves based on changes between the object's original and evaluated "
"surface mesh";
ntype.enum_name_legacy = "DEFORM_CURVES_ON_SURFACE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
ntype.default_width = bke::NodeWidth::_180;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_deform_curves_on_surface_cc

View File

@@ -0,0 +1,137 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_separate_geometry.hh"
#include "RNA_enum_types.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_delete_geometry_cc {
NODE_STORAGE_FUNCS(NodeGeometryDeleteGeometry)
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
b.add_input<decl::Geometry>("Geometry"_ustr).description("Geometry to delete elements from");
b.add_output<decl::Geometry>("Geometry"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field()
.description("The parts of the geometry to be deleted");
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
const bNode *node = static_cast<bNode *>(ptr->data);
const NodeGeometryDeleteGeometry &storage = node_storage(*node);
const AttrDomain domain = AttrDomain(storage.domain);
layout.prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
/* Only show the mode when it is relevant. */
if (ELEM(domain, AttrDomain::Point, AttrDomain::Edge, AttrDomain::Face)) {
layout.prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryDeleteGeometry *data = MEM_new<NodeGeometryDeleteGeometry>(__func__);
data->domain = int(AttrDomain::Point);
data->mode = GEO_NODE_DELETE_GEOMETRY_MODE_ALL;
node->storage = data;
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"_ustr);
/* The node's input is a selection of elements that should be deleted, but the code is
* implemented as a separation operation that copies the selected elements to a new geometry.
* Invert the selection to avoid the need to keep track of both cases in the code. */
const Field<bool> selection = fn::invert_boolean_field(
params.extract_input<Field<bool>>("Selection"_ustr));
const NodeGeometryDeleteGeometry &storage = node_storage(params.node());
const AttrDomain domain = AttrDomain(storage.domain);
const GeometryNodeDeleteGeometryMode mode = GeometryNodeDeleteGeometryMode(storage.mode);
const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Geometry"_ustr);
if (domain == AttrDomain::Instance) {
bool is_error;
geometry::separate_geometry(geometry_set, domain, mode, selection, attribute_filter, is_error);
}
else {
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
bool is_error;
/* Invert here because we want to keep the things not in the selection. */
geometry::separate_geometry(
geometry_set, domain, mode, selection, attribute_filter, is_error);
});
}
params.set_output("Geometry"_ustr, std::move(geometry_set));
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem mode_items[] = {
{GEO_NODE_DELETE_GEOMETRY_MODE_ALL, "ALL", 0, "All", ""},
{GEO_NODE_DELETE_GEOMETRY_MODE_EDGE_FACE, "EDGE_FACE", 0, "Only Edges & Faces", ""},
{GEO_NODE_DELETE_GEOMETRY_MODE_ONLY_FACE, "ONLY_FACE", 0, "Only Faces", ""},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"mode",
"Mode",
"Which parts of the mesh component to delete",
mode_items,
NOD_storage_enum_accessors(mode),
GEO_NODE_DELETE_GEOMETRY_MODE_ALL);
RNA_def_node_enum(srna,
"domain",
"Domain",
"Which domain to delete in",
rna_enum_attribute_domain_without_corner_items,
NOD_storage_enum_accessors(domain),
int(AttrDomain::Point));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeDeleteGeometry"_ustr, GEO_NODE_DELETE_GEOMETRY);
ntype.ui_name = "Delete Geometry";
ntype.ui_description = "Remove selected elements of a geometry";
ntype.enum_name_legacy = "DELETE_GEOMETRY";
ntype.nclass = NODE_CLASS_GEOMETRY;
bke::node_type_storage(
ntype, "NodeGeometryDeleteGeometry", node_free_standard_storage, node_copy_standard_storage);
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_delete_geometry_cc

View File

@@ -0,0 +1,275 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifdef WITH_OPENVDB
# include <openvdb/openvdb.h>
# include <openvdb/tools/Interpolation.h>
# include <openvdb/tools/PointScatter.h>
# include <algorithm>
#endif
#include "DNA_node_types.h"
#include "DNA_pointcloud_types.h"
#include "BKE_pointcloud.hh"
#include "BKE_volume_grid.hh"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_distribute_points_in_grid_cc {
enum class DistributeMode {
Random = 0,
Grid = 1,
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>("Grid"_ustr).hide_value().structure_type(StructureType::Grid);
auto &density = b.add_input<decl::Float>("Density"_ustr)
.default_value(1.0f)
.min(0.0f)
.max(100000.0f)
.subtype(PROP_NONE)
.description(
"When combined with each voxel's value, determines the number of points "
"to sample per unit volume")
.make_available(
[](bNode &node) { node.custom1 = int16_t(DistributeMode::Random); });
auto &seed =
b.add_input<decl::Int>("Seed"_ustr)
.min(-10000)
.max(10000)
.description("Seed used by the random number generator to generate random points")
.make_available([](bNode &node) { node.custom1 = int16_t(DistributeMode::Random); });
auto &spacing = b.add_input<decl::Vector>("Spacing"_ustr)
.default_value({0.3, 0.3, 0.3})
.min(0.0001f)
.subtype(PROP_XYZ)
.description("Spacing between grid points")
.make_available(
[](bNode &node) { node.custom1 = int16_t(DistributeMode::Grid); });
auto &threshold = b.add_input<decl::Float>("Threshold"_ustr)
.default_value(0.1f)
.min(0.0f)
.max(FLT_MAX)
.description("Minimum density of a voxel to contain a grid point")
.make_available(
[](bNode &node) { node.custom1 = int16_t(DistributeMode::Grid); });
b.add_output<decl::Geometry>("Points"_ustr).propagate_all_geometry();
const bNode *node = b.node_or_null();
if (node != nullptr) {
const auto mode = DistributeMode(node->custom1);
density.available(mode == DistributeMode::Random);
seed.available(mode == DistributeMode::Random);
spacing.available(mode == DistributeMode::Grid);
threshold.available(mode == DistributeMode::Grid);
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = int16_t(DistributeMode::Random);
}
#ifdef WITH_OPENVDB
/* Implements the interface required by #openvdb::tools::NonUniformPointScatter. */
class PositionsVDBWrapper {
private:
Vector<float3> &vector_;
public:
PositionsVDBWrapper(Vector<float3> &vector) : vector_(vector) {}
PositionsVDBWrapper(const PositionsVDBWrapper &wrapper) = default;
void add(const openvdb::Vec3R &pos)
{
vector_.append(float3(float(pos[0]), float(pos[1]), float(pos[2])));
}
};
/* Use #std::mt19937 as a random number generator. It has a very long period and thus there should
* be no visible patterns in the generated points. */
using RNGType = std::mt19937;
/* Non-uniform scatter allows the amount of points to be scaled with the volume's density. */
using NonUniformPointScatterVDB =
openvdb::tools::NonUniformPointScatter<PositionsVDBWrapper, RNGType>;
static void point_scatter_density_random(const openvdb::FloatGrid &grid,
const float density,
const int seed,
Vector<float3> &r_positions)
{
/* Setup and call into OpenVDB's point scatter API. */
PositionsVDBWrapper vdb_position_wrapper(r_positions);
RNGType random_generator(seed);
NonUniformPointScatterVDB point_scatter(vdb_position_wrapper, density, random_generator);
point_scatter(grid);
}
static void point_scatter_density_grid(const openvdb::FloatGrid &grid,
const float3 spacing,
const float threshold,
Vector<float3> &r_positions)
{
const openvdb::Vec3d half_voxel(0.5, 0.5, 0.5);
const openvdb::Vec3d voxel_spacing(double(spacing.x) / grid.voxelSize().x(),
double(spacing.y) / grid.voxelSize().y(),
double(spacing.z) / grid.voxelSize().z());
/* Abort if spacing is zero. */
const double min_spacing = std::min({voxel_spacing.x(), voxel_spacing.y(), voxel_spacing.z()});
if (std::abs(min_spacing) < 0.0001) {
return;
}
/* Iterate through tiles and voxels on the grid. */
for (openvdb::FloatGrid::ValueOnCIter cell = grid.cbeginValueOn(); cell; ++cell) {
/* Check if the cell's value meets the minimum threshold. */
if (cell.getValue() < threshold) {
continue;
}
/* Compute the bounding box of each tile/voxel. */
const openvdb::CoordBBox bbox = cell.getBoundingBox();
const openvdb::Vec3d box_min = bbox.min().asVec3d() - half_voxel;
const openvdb::Vec3d box_max = bbox.max().asVec3d() + half_voxel;
/* Pick a starting point rounded up to the nearest possible point. */
double abs_spacing_x = std::abs(voxel_spacing.x());
double abs_spacing_y = std::abs(voxel_spacing.y());
double abs_spacing_z = std::abs(voxel_spacing.z());
const openvdb::Vec3d start(ceil(box_min.x() / abs_spacing_x) * abs_spacing_x,
ceil(box_min.y() / abs_spacing_y) * abs_spacing_y,
ceil(box_min.z() / abs_spacing_z) * abs_spacing_z);
/* Iterate through all possible points in box. */
for (double x = start.x(); x < box_max.x(); x += abs_spacing_x) {
for (double y = start.y(); y < box_max.y(); y += abs_spacing_y) {
for (double z = start.z(); z < box_max.z(); z += abs_spacing_z) {
/* Transform with grid matrix and add point. */
const openvdb::Vec3d idx_pos(x, y, z);
const openvdb::Vec3d local_pos = grid.indexToWorld(idx_pos);
r_positions.append({float(local_pos.x()), float(local_pos.y()), float(local_pos.z())});
}
}
}
}
}
#endif /* WITH_OPENVDB */
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
const bke::VolumeGrid<float> volume_grid = params.extract_input<bke::VolumeGrid<float>>(
"Grid"_ustr);
if (!volume_grid) {
params.set_default_remaining_outputs();
return;
}
bke::VolumeTreeAccessToken tree_token;
const openvdb::GridBase &base_grid = volume_grid.grid(tree_token);
if (!base_grid.isType<openvdb::FloatGrid>()) {
params.set_default_remaining_outputs();
return;
}
const openvdb::FloatGrid &grid = static_cast<const openvdb::FloatGrid &>(base_grid);
const DistributeMode mode = DistributeMode(params.node().custom1);
float density;
int seed;
float3 spacing{0, 0, 0};
float threshold;
if (mode == DistributeMode::Random) {
density = params.extract_input<float>("Density"_ustr);
seed = params.extract_input<int>("Seed"_ustr);
}
else if (mode == DistributeMode::Grid) {
spacing = params.extract_input<float3>("Spacing"_ustr);
threshold = params.extract_input<float>("Threshold"_ustr);
}
Vector<float3> positions;
switch (mode) {
case DistributeMode::Random:
point_scatter_density_random(grid, density, seed, positions);
break;
case DistributeMode::Grid:
point_scatter_density_grid(grid, spacing, threshold, positions);
break;
}
PointCloud *pointcloud = BKE_pointcloud_new_nomain(positions.size());
pointcloud->positions_for_write().copy_from(positions);
geometry::debug_randomize_point_order(pointcloud);
params.set_output("Points"_ustr, GeometrySet::from_pointcloud(pointcloud));
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem mode_items[] = {
{int(DistributeMode::Random),
"DENSITY_RANDOM",
0,
"Random",
"Distribute points randomly inside of the volume"},
{int(DistributeMode::Grid),
"DENSITY_GRID",
0,
"Grid",
"Distribute the points in a grid pattern inside of the volume"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"mode",
"Distribution Method",
"Method to use for scattering points",
mode_items,
NOD_inline_enum_accessors(custom1),
int(DistributeMode::Random));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeDistributePointsInGrid"_ustr, GEO_NODE_DISTRIBUTE_POINTS_IN_GRID);
ntype.ui_name = "Distribute Points in Grid";
ntype.ui_description = "Generate points inside a volume grid";
ntype.enum_name_legacy = "DISTRIBUTE_POINTS_IN_GRID";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.initfunc = node_init;
ntype.default_width = bke::NodeWidth::_180;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_distribute_points_in_grid_cc

View File

@@ -0,0 +1,265 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifdef WITH_OPENVDB
# include <openvdb/openvdb.h>
# include <openvdb/tools/Interpolation.h>
# include <openvdb/tools/PointScatter.h>
# include <algorithm>
#endif
#include "DNA_node_types.h"
#include "DNA_pointcloud_types.h"
#include "BKE_pointcloud.hh"
#include "BKE_volume.hh"
#include "BKE_volume_grid.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_distribute_points_in_volume_cc {
NODE_STORAGE_FUNCS(NodeGeometryDistributePointsInVolume)
static const EnumPropertyItem mode_items[] = {
{GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM,
"DENSITY_RANDOM",
0,
N_("Random"),
N_("Distribute points randomly inside of the volume")},
{GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_GRID,
"DENSITY_GRID",
0,
N_("Grid"),
N_("Distribute the points in a grid pattern inside of the volume")},
{0, nullptr, 0, nullptr, nullptr},
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Volume"_ustr)
.supported_type(GeometryComponent::Type::Volume)
.translation_context(BLT_I18NCONTEXT_ID_ID)
.description("Volume with fog grids that points are scattered in");
b.add_input<decl::Menu>("Mode"_ustr)
.static_items(mode_items)
.optional_label()
.description("Method to use for scattering points");
b.add_input<decl::Float>("Density"_ustr)
.default_value(1.0f)
.min(0.0f)
.max(100000.0f)
.subtype(PROP_NONE)
.description("Number of points to sample per unit volume")
.usage_by_single_menu(GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM);
b.add_input<decl::Int>("Seed"_ustr)
.min(-10000)
.max(10000)
.description("Seed used by the random number generator to generate random points")
.usage_by_single_menu(GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM);
b.add_input<decl::Vector>("Spacing"_ustr)
.default_value({0.3, 0.3, 0.3})
.min(0.0001f)
.subtype(PROP_XYZ)
.description("Spacing between grid points")
.usage_by_single_menu(GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_GRID);
b.add_input<decl::Float>("Threshold"_ustr)
.default_value(0.1f)
.min(0.0f)
.max(FLT_MAX)
.description("Minimum density of a volume cell to contain a grid point")
.usage_by_single_menu(GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_GRID);
b.add_output<decl::Geometry>("Points"_ustr).propagate_all_geometry();
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
/* Still used for forward compatibility. */
node->storage = MEM_new<NodeGeometryDistributePointsInVolume>(__func__);
}
#ifdef WITH_OPENVDB
/* Implements the interface required by #openvdb::tools::NonUniformPointScatter. */
class PositionsVDBWrapper {
private:
Vector<float3> &vector_;
public:
PositionsVDBWrapper(Vector<float3> &vector) : vector_(vector) {}
PositionsVDBWrapper(const PositionsVDBWrapper &wrapper) = default;
void add(const openvdb::Vec3R &pos)
{
vector_.append(float3(float(pos[0]), float(pos[1]), float(pos[2])));
}
};
/* Use #std::mt19937 as a random number generator,
* it has a very long period and thus there should be no visible patterns in the generated points.
*/
using RNGType = std::mt19937;
/* Non-uniform scatter allows the amount of points to be scaled with the volume's density. */
using NonUniformPointScatterVDB =
openvdb::tools::NonUniformPointScatter<PositionsVDBWrapper, RNGType>;
static void point_scatter_density_random(const openvdb::FloatGrid &grid,
const float density,
const int seed,
Vector<float3> &r_positions)
{
/* Setup and call into OpenVDB's point scatter API. */
PositionsVDBWrapper vdb_position_wrapper = PositionsVDBWrapper(r_positions);
RNGType random_generator(seed);
NonUniformPointScatterVDB point_scatter(vdb_position_wrapper, density, random_generator);
point_scatter(grid);
}
static void point_scatter_density_grid(const openvdb::FloatGrid &grid,
const float3 spacing,
const float threshold,
Vector<float3> &r_positions)
{
const openvdb::Vec3d half_voxel(0.5, 0.5, 0.5);
const openvdb::Vec3d voxel_spacing(double(spacing.x) / grid.voxelSize().x(),
double(spacing.y) / grid.voxelSize().y(),
double(spacing.z) / grid.voxelSize().z());
/* Abort if spacing is zero. */
const double min_spacing = std::min({voxel_spacing.x(), voxel_spacing.y(), voxel_spacing.z()});
if (std::abs(min_spacing) < 0.0001) {
return;
}
/* Iterate through tiles and voxels on the grid. */
for (openvdb::FloatGrid::ValueOnCIter cell = grid.cbeginValueOn(); cell; ++cell) {
/* Check if the cell's value meets the minimum threshold. */
if (cell.getValue() < threshold) {
continue;
}
/* Compute the bounding box of each tile/voxel. */
const openvdb::CoordBBox bbox = cell.getBoundingBox();
const openvdb::Vec3d box_min = bbox.min().asVec3d() - half_voxel;
const openvdb::Vec3d box_max = bbox.max().asVec3d() + half_voxel;
/* Pick a starting point rounded up to the nearest possible point. */
double abs_spacing_x = std::abs(voxel_spacing.x());
double abs_spacing_y = std::abs(voxel_spacing.y());
double abs_spacing_z = std::abs(voxel_spacing.z());
const openvdb::Vec3d start(ceil(box_min.x() / abs_spacing_x) * abs_spacing_x,
ceil(box_min.y() / abs_spacing_y) * abs_spacing_y,
ceil(box_min.z() / abs_spacing_z) * abs_spacing_z);
/* Iterate through all possible points in box. */
for (double x = start.x(); x < box_max.x(); x += abs_spacing_x) {
for (double y = start.y(); y < box_max.y(); y += abs_spacing_y) {
for (double z = start.z(); z < box_max.z(); z += abs_spacing_z) {
/* Transform with grid matrix and add point. */
const openvdb::Vec3d idx_pos(x, y, z);
const openvdb::Vec3d local_pos = grid.indexToWorld(idx_pos);
r_positions.append({float(local_pos.x()), float(local_pos.y()), float(local_pos.z())});
}
}
}
}
}
#endif /* WITH_OPENVDB */
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
GeometrySet geometry_set = params.extract_input<GeometrySet>("Volume"_ustr);
const auto mode = params.extract_input<GeometryNodeDistributePointsInVolumeMode>("Mode"_ustr);
float density;
int seed;
float3 spacing{0, 0, 0};
float threshold;
if (mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM) {
density = params.extract_input<float>("Density"_ustr);
seed = params.extract_input<int>("Seed"_ustr);
}
else if (mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_GRID) {
spacing = params.extract_input<float3>("Spacing"_ustr);
threshold = params.extract_input<float>("Threshold"_ustr);
}
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
if (!geometry_set.has_volume()) {
geometry_set.keep_only({GeometryComponent::Type::Edit});
return;
}
const VolumeComponent *component = geometry_set.get_component<VolumeComponent>();
const Volume *volume = component->get();
BKE_volume_load(volume, params.bmain());
Vector<float3> positions;
for (const int i : IndexRange(BKE_volume_num_grids(volume))) {
const bke::VolumeGridData *volume_grid = BKE_volume_grid_get(volume, i);
if (volume_grid == nullptr) {
continue;
}
bke::VolumeTreeAccessToken tree_token;
const openvdb::GridBase &base_grid = volume_grid->grid(tree_token);
if (!base_grid.isType<openvdb::FloatGrid>()) {
continue;
}
const openvdb::FloatGrid &grid = static_cast<const openvdb::FloatGrid &>(base_grid);
if (mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM) {
point_scatter_density_random(grid, density, seed, positions);
}
else if (mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_GRID) {
point_scatter_density_grid(grid, spacing, threshold, positions);
}
}
PointCloud *pointcloud = BKE_pointcloud_new_nomain(positions.size());
bke::MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write();
pointcloud->positions_for_write().copy_from(positions);
point_attributes.add<float>("radius", bke::AttrDomain::Point, bke::AttributeInitValue(0.05f));
geometry::debug_randomize_point_order(pointcloud);
geometry_set.replace_pointcloud(pointcloud);
geometry_set.keep_only({GeometryComponent::Type::PointCloud, GeometryComponent::Type::Edit});
});
params.set_output("Points"_ustr, std::move(geometry_set));
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeDistributePointsInVolume"_ustr, GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME);
ntype.ui_name = "Distribute Points in Volume";
ntype.ui_description = "Generate points inside a volume";
ntype.enum_name_legacy = "DISTRIBUTE_POINTS_IN_VOLUME";
ntype.nclass = NODE_CLASS_GEOMETRY;
bke::node_type_storage(ntype,
"NodeGeometryDistributePointsInVolume",
node_free_standard_storage,
node_copy_standard_storage);
ntype.initfunc = node_init;
ntype.default_width = bke::NodeWidth::_180;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_distribute_points_in_volume_cc

View File

@@ -0,0 +1,714 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_kdtree.hh"
#include "BLI_math_geom.h"
#include "BLI_math_quaternion.hh"
#include "BLI_math_rotation.h"
#include "BLI_noise.hh"
#include "BLI_rand.hh"
#include "BLI_task.hh"
#include "DNA_pointcloud_types.h"
#include "BKE_attribute_math.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_sample.hh"
#include "BKE_pointcloud.hh"
#include "FN_multi_function_registry.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_distribute_points_on_faces_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
auto enable_random = [](bNode &node) {
node.custom1 = GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM;
};
auto enable_poisson = [](bNode &node) {
node.custom1 = GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON;
};
b.add_input<decl::Geometry>("Mesh"_ustr)
.supported_type(GeometryComponent::Type::Mesh)
.description("Mesh on whose faces to distribute points on");
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field();
auto &distance_min = b.add_input<decl::Float>("Distance Min"_ustr)
.min(0.0f)
.subtype(PROP_DISTANCE)
.make_available(enable_poisson)
.available(false);
auto &density_max = b.add_input<decl::Float>("Density Max"_ustr)
.default_value(10.0f)
.min(0.0f)
.make_available(enable_poisson)
.available(false);
auto &density = b.add_input<decl::Float>("Density"_ustr)
.default_value(10.0f)
.min(0.0f)
.evaluated_geometry_field()
.make_available(enable_random)
.available(false);
auto &density_factor = b.add_input<decl::Float>("Density Factor"_ustr)
.default_value(1.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.evaluated_geometry_field()
.make_available(enable_poisson)
.available(false);
b.add_input<decl::Int>("Seed"_ustr);
b.add_output<decl::Geometry>("Points"_ustr).propagate_all_geometry();
b.add_output<decl::Vector>("Normal"_ustr).anonymous_attribute_output();
b.add_output<decl::Rotation>("Rotation"_ustr).anonymous_attribute_output();
const bNode *node = b.node_or_null();
if (node != nullptr) {
switch (node->custom1) {
case GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON:
distance_min.available(true);
density_max.available(true);
density_factor.available(true);
break;
case GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM:
density.available(true);
break;
}
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "distribute_method", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_layout_ex(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "use_legacy_normal", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
/**
* Use an arbitrary choice of axes for a usable rotation attribute directly out of this node.
*/
static math::Quaternion normal_to_rotation(const float3 normal)
{
float quat[4];
vec_to_quat(quat, normal, OB_NEGZ, OB_POSY);
return math::normalize(math::Quaternion(quat));
}
static OffsetIndices<int> calc_tri_point_offsets(const Mesh &mesh,
const Span<float> densities,
const int seed,
Array<int> &r_count_data)
{
const Span<float3> positions = mesh.vert_positions();
const Span<int> corner_verts = mesh.corner_verts();
const Span<int3> corner_tris = mesh.corner_tris();
r_count_data = Array<int>(corner_tris.size() + 1);
threading::parallel_for(corner_tris.index_range(), 1024, [&](const IndexRange range) {
for (const int64_t tri_i : range) {
const int3 &tri = corner_tris[tri_i];
const float density = (densities[tri[0]] + densities[tri[1]] + densities[tri[2]]) / 3.0f;
const float area = area_tri_v3(positions[corner_verts[tri[0]]],
positions[corner_verts[tri[1]]],
positions[corner_verts[tri[2]]]);
const int corner_tri_seed = noise::hash(tri_i, seed);
RandomNumberGenerator corner_tri_rng(corner_tri_seed);
r_count_data[tri_i] = corner_tri_rng.round_probabilistic(area * density);
}
});
return offset_indices::accumulate_counts_to_offsets(r_count_data);
}
static OffsetIndices<int> calc_tri_point_offsets(const Mesh &mesh,
const float density,
const int seed,
Array<int> &r_count_data)
{
const Span<float3> positions = mesh.vert_positions();
const Span<int> corner_verts = mesh.corner_verts();
const Span<int3> corner_tris = mesh.corner_tris();
r_count_data = Array<int>(corner_tris.size() + 1);
threading::parallel_for(corner_tris.index_range(), 1024, [&](const IndexRange range) {
for (const int64_t tri_i : range) {
const int3 &tri = corner_tris[tri_i];
const float area = area_tri_v3(positions[corner_verts[tri[0]]],
positions[corner_verts[tri[1]]],
positions[corner_verts[tri[2]]]);
const int corner_tri_seed = noise::hash(tri_i, seed);
RandomNumberGenerator corner_tri_rng(corner_tri_seed);
r_count_data[tri_i] = corner_tri_rng.round_probabilistic(area * density);
}
});
return offset_indices::accumulate_counts_to_offsets(r_count_data);
}
static void sample_bary_coords(const Mesh &mesh,
const int seed,
const OffsetIndices<int> points_by_tri,
Vector<float3> &r_positions,
Vector<float3> &r_bary_coords,
Vector<int> &r_tri_indices)
{
const Span<float3> positions = mesh.vert_positions();
const Span<int> corner_verts = mesh.corner_verts();
const Span<int3> corner_tris = mesh.corner_tris();
r_positions.resize(points_by_tri.total_size());
r_bary_coords.resize(points_by_tri.total_size());
r_tri_indices.resize(points_by_tri.total_size());
threading::parallel_for(
corner_tris.index_range(),
4096,
[&](const IndexRange range) {
for (const int64_t tri_i : range) {
const int3 &tri = corner_tris[tri_i];
const float3 &v0_pos = positions[corner_verts[tri[0]]];
const float3 &v1_pos = positions[corner_verts[tri[1]]];
const float3 &v2_pos = positions[corner_verts[tri[2]]];
const int corner_tri_seed = noise::hash(tri_i, seed);
RandomNumberGenerator corner_tri_rng(corner_tri_seed);
/* Retain legacy behavior. */
corner_tri_rng.skip(1);
for (const int i : points_by_tri[tri_i]) {
const float3 bary_coord = corner_tri_rng.get_barycentric_coordinates();
r_positions[i] = bke::attribute_math::mix3(bary_coord, v0_pos, v1_pos, v2_pos);
r_bary_coords[i] = bary_coord;
r_tri_indices[i] = tri_i;
}
}
},
threading::accumulated_task_sizes(
[&](const IndexRange range) { return points_by_tri[range].size(); }));
}
BLI_NOINLINE static KDTree<float3> *build_kdtree(Span<float3> positions)
{
KDTree<float3> *kdtree = kdtree_new<float3>(positions.size());
int i_point = 0;
for (const float3 position : positions) {
kdtree_insert<float3>(kdtree, i_point, position);
i_point++;
}
kdtree_balance<float3>(kdtree);
return kdtree;
}
BLI_NOINLINE static void update_elimination_mask_for_close_points(
Span<float3> positions, const float minimum_distance, MutableSpan<bool> elimination_mask)
{
if (minimum_distance <= 0.0f) {
return;
}
KDTree<float3> *kdtree = build_kdtree(positions);
BLI_SCOPED_DEFER([&]() { kdtree_free<float3>(kdtree); });
for (const int i : positions.index_range()) {
if (elimination_mask[i]) {
continue;
}
kdtree_range_search_cb<float3>(kdtree,
positions[i],
minimum_distance,
[&](int index, const float3 & /*co*/, float /*dist_sq*/) {
if (index != i) {
elimination_mask[index] = true;
}
return true;
});
}
}
BLI_NOINLINE static void update_elimination_mask_based_on_density_factors(
const Mesh &mesh,
const Span<float> density_factors,
const Span<float3> bary_coords,
const Span<int> tri_indices,
const MutableSpan<bool> elimination_mask)
{
const Span<int3> corner_tris = mesh.corner_tris();
for (const int i : bary_coords.index_range()) {
if (elimination_mask[i]) {
continue;
}
const int3 &tri = corner_tris[tri_indices[i]];
const float3 bary_coord = bary_coords[i];
const float v0_density_factor = density_factors[tri[0]];
const float v1_density_factor = density_factors[tri[1]];
const float v2_density_factor = density_factors[tri[2]];
const float probability = v0_density_factor * bary_coord.x + v1_density_factor * bary_coord.y +
v2_density_factor * bary_coord.z;
const float hash = noise::hash_float_to_float(bary_coord);
if (hash > probability) {
elimination_mask[i] = true;
}
}
}
BLI_NOINLINE static void eliminate_points_based_on_mask(const Span<bool> elimination_mask,
Vector<float3> &positions,
Vector<float3> &bary_coords,
Vector<int> &tri_indices)
{
for (int i = positions.size() - 1; i >= 0; i--) {
if (elimination_mask[i]) {
positions.remove_and_reorder(i);
bary_coords.remove_and_reorder(i);
tri_indices.remove_and_reorder(i);
}
}
}
BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh,
const Span<float3> bary_coords,
const Span<int> tri_indices,
const AttrDomain source_domain,
const GVArray &source_data,
GMutableSpan output_data)
{
switch (source_domain) {
case AttrDomain::Point: {
bke::mesh_surface_sample::sample_point_attribute(mesh.corner_verts(),
mesh.corner_tris(),
tri_indices,
bary_coords,
source_data,
IndexMask(output_data.size()),
output_data);
break;
}
case AttrDomain::Corner: {
bke::mesh_surface_sample::sample_corner_attribute(mesh.corner_tris(),
tri_indices,
bary_coords,
source_data,
IndexMask(output_data.size()),
output_data);
break;
}
case AttrDomain::Face: {
bke::mesh_surface_sample::sample_face_attribute(mesh.corner_tri_faces(),
tri_indices,
source_data,
IndexMask(output_data.size()),
output_data);
break;
}
default: {
/* Not supported currently. */
return;
}
}
}
BLI_NOINLINE static void propagate_attributes(const Mesh &mesh,
const bke::AttributeFilter &filter,
PointCloud &points,
const Span<float3> bary_coords,
const Span<int> tri_indices)
{
const AttributeAccessor mesh_attributes = mesh.attributes();
MutableAttributeAccessor point_attributes = points.attributes_for_write();
mesh_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.domain == AttrDomain::Edge) {
return;
}
const StringRef name = iter.name;
if (iter.is_builtin && !point_attributes.is_builtin(name)) {
return;
}
if (name == "position") {
return;
}
if (filter.allow_skip(name)) {
return;
}
GAttributeReader src = iter.get();
if (!src) {
return;
}
const CommonVArrayInfo info = src.varray.common_info();
if (info.type == CommonVArrayInfo::Type::Single) {
const bke::AttributeInitValue init(GPointer(src.varray.type(), info.data));
if (point_attributes.add(iter.name, AttrDomain::Point, iter.data_type, init)) {
return;
}
}
GSpanAttributeWriter dst = point_attributes.lookup_or_add_for_write_only_span(
name, AttrDomain::Point, iter.data_type);
if (!dst) {
return;
}
interpolate_attribute(mesh, bary_coords, tri_indices, src.domain, src.varray, dst.span);
dst.finish();
});
}
namespace {
struct AttributeOutputs {
std::optional<std::string> normal_id;
std::optional<std::string> rotation_id;
};
} // namespace
static void compute_normal_outputs(const Mesh &mesh,
const Span<float3> bary_coords,
const Span<int> tri_indices,
MutableSpan<float3> r_normals)
{
switch (mesh.normals_domain()) {
case bke::MeshNormalDomain::Point: {
const Span<int> corner_verts = mesh.corner_verts();
const Span<int3> corner_tris = mesh.corner_tris();
const Span<float3> vert_normals = mesh.vert_normals();
threading::parallel_for(bary_coords.index_range(), 512, [&](const IndexRange range) {
bke::mesh_surface_sample::sample_point_normals(
corner_verts, corner_tris, tri_indices, bary_coords, vert_normals, range, r_normals);
});
break;
}
case bke::MeshNormalDomain::Face: {
const Span<int> tri_faces = mesh.corner_tri_faces();
VArray<float3> face_normals = VArray<float3>::from_span(mesh.face_normals());
threading::parallel_for(bary_coords.index_range(), 512, [&](const IndexRange range) {
bke::mesh_surface_sample::sample_face_attribute(
tri_faces, tri_indices, face_normals, range, r_normals);
});
break;
}
case bke::MeshNormalDomain::Corner: {
const Span<int3> corner_tris = mesh.corner_tris();
const Span<float3> corner_normals = mesh.corner_normals();
threading::parallel_for(bary_coords.index_range(), 512, [&](const IndexRange range) {
bke::mesh_surface_sample::sample_corner_normals(
corner_tris, tri_indices, bary_coords, corner_normals, range, r_normals);
});
break;
}
}
}
static void compute_legacy_normal_outputs(const Mesh &mesh,
const Span<float3> bary_coords,
const Span<int> tri_indices,
MutableSpan<float3> r_normals)
{
const Span<float3> positions = mesh.vert_positions();
const Span<int> corner_verts = mesh.corner_verts();
const Span<int3> corner_tris = mesh.corner_tris();
for (const int i : bary_coords.index_range()) {
const int tri_i = tri_indices[i];
const int3 &tri = corner_tris[tri_i];
const int v0_index = corner_verts[tri[0]];
const int v1_index = corner_verts[tri[1]];
const int v2_index = corner_verts[tri[2]];
const float3 v0_pos = positions[v0_index];
const float3 v1_pos = positions[v1_index];
const float3 v2_pos = positions[v2_index];
float3 normal;
normal_tri_v3(normal, v0_pos, v1_pos, v2_pos);
r_normals[i] = normal;
}
}
static void compute_rotation_output(const Span<float3> normals,
MutableSpan<math::Quaternion> r_rotations)
{
threading::parallel_for(normals.index_range(), 512, [&](const IndexRange range) {
for (const int i : range) {
r_rotations[i] = normal_to_rotation(normals[i]);
}
});
}
BLI_NOINLINE static void compute_attribute_outputs(const Mesh &mesh,
PointCloud &points,
const Span<float3> bary_coords,
const Span<int> tri_indices,
const AttributeOutputs &attribute_outputs,
const bool use_legacy_normal)
{
MutableAttributeAccessor point_attributes = points.attributes_for_write();
SpanAttributeWriter<int> ids = point_attributes.lookup_or_add_for_write_only_span<int>(
"id", AttrDomain::Point);
SpanAttributeWriter<float3> normals;
SpanAttributeWriter<math::Quaternion> rotations;
if (attribute_outputs.normal_id) {
normals = point_attributes.lookup_or_add_for_write_only_span<float3>(
*attribute_outputs.normal_id, AttrDomain::Point);
}
if (attribute_outputs.rotation_id) {
rotations = point_attributes.lookup_or_add_for_write_only_span<math::Quaternion>(
*attribute_outputs.rotation_id, AttrDomain::Point);
}
threading::parallel_for(bary_coords.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
const int tri_i = tri_indices[i];
const float3 &bary_coord = bary_coords[i];
ids.span[i] = noise::hash(noise::hash_float(bary_coord), tri_i);
}
});
if (normals) {
if (use_legacy_normal) {
compute_legacy_normal_outputs(mesh, bary_coords, tri_indices, normals.span);
}
else {
compute_normal_outputs(mesh, bary_coords, tri_indices, normals.span);
}
if (rotations) {
compute_rotation_output(normals.span, rotations.span);
}
}
ids.finish();
normals.finish();
rotations.finish();
}
static Array<float> calc_full_density_factors_with_selection(const Mesh &mesh,
const Field<float> &density_field,
const Field<bool> &selection_field)
{
const AttrDomain domain = AttrDomain::Corner;
const int domain_size = mesh.attributes().domain_size(domain);
Array<float> densities(domain_size, 0.0f);
const bke::MeshFieldContext field_context{mesh, domain};
fn::FieldEvaluator evaluator{field_context, domain_size};
evaluator.set_selection(selection_field);
evaluator.add_with_destination(density_field, densities.as_mutable_span());
evaluator.evaluate();
return densities;
}
static PointCloud *create_points_random(const Mesh &mesh,
const Field<bool> &selection_field,
const Field<float> &density_field,
const int seed,
const AttributeOutputs &attribute_outputs,
const bke::AttributeFilter &attribute_filter,
const bool use_legacy_normal)
{
Array<int> count_data;
OffsetIndices<int> points_by_tri;
if (selection_field.depends_on_input() || density_field.depends_on_input()) {
const Array<float> densities = calc_full_density_factors_with_selection(
mesh, density_field, selection_field);
points_by_tri = calc_tri_point_offsets(mesh, densities, seed, count_data);
}
else {
const float density = fn::evaluate_constant_field<float>(density_field);
points_by_tri = calc_tri_point_offsets(mesh, density, seed, count_data);
}
if (points_by_tri.total_size() == 0) {
return nullptr;
}
Vector<float3> positions;
Vector<float3> bary_coords;
Vector<int> tri_indices;
sample_bary_coords(mesh, seed, points_by_tri, positions, bary_coords, tri_indices);
PointCloud *pointcloud = bke::pointcloud_new_no_attributes(positions.size());
bke::MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write();
VectorData<float3, GuardedAllocator> positions_data = positions.release();
const auto *attr_data = implicit_sharing::info_for_mem_free(positions_data.data);
point_attributes.add<float3>("position",
bke::AttrDomain::Point,
bke::AttributeInitShared(positions_data.data, *attr_data));
attr_data->remove_user_and_delete_if_last();
point_attributes.add<float>("radius", bke::AttrDomain::Point, bke::AttributeInitValue(0.05f));
propagate_attributes(mesh, attribute_filter, *pointcloud, bary_coords, tri_indices);
compute_attribute_outputs(
mesh, *pointcloud, bary_coords, tri_indices, attribute_outputs, use_legacy_normal);
geometry::debug_randomize_point_order(pointcloud);
return pointcloud;
}
static PointCloud *create_points_poisson_disk(const Mesh &mesh,
const Field<bool> &selection_field,
const Field<float> &density_factor_field,
const float minimum_distance,
const float density_max,
const int seed,
const AttributeOutputs &attribute_outputs,
const bke::AttributeFilter &attribute_filter,
const bool use_legacy_normal)
{
Array<int> count_data;
const OffsetIndices<int> points_by_tri = calc_tri_point_offsets(
mesh, density_max, seed, count_data);
if (points_by_tri.total_size() == 0) {
return nullptr;
}
Vector<float3> positions;
Vector<float3> bary_coords;
Vector<int> tri_indices;
sample_bary_coords(mesh, seed, points_by_tri, positions, bary_coords, tri_indices);
Array<bool> elimination_mask(positions.size(), false);
update_elimination_mask_for_close_points(positions, minimum_distance, elimination_mask);
const Array<float> density_factors = calc_full_density_factors_with_selection(
mesh, density_factor_field, selection_field);
update_elimination_mask_based_on_density_factors(
mesh, density_factors, bary_coords, tri_indices, elimination_mask.as_mutable_span());
eliminate_points_based_on_mask(elimination_mask.as_span(), positions, bary_coords, tri_indices);
if (positions.is_empty()) {
return nullptr;
}
PointCloud *pointcloud = bke::pointcloud_new_no_attributes(positions.size());
bke::MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write();
pointcloud->positions_for_write().copy_from(positions);
point_attributes.add<float>("radius", bke::AttrDomain::Point, bke::AttributeInitValue(0.05f));
propagate_attributes(mesh, attribute_filter, *pointcloud, bary_coords, tri_indices);
compute_attribute_outputs(
mesh, *pointcloud, bary_coords, tri_indices, attribute_outputs, use_legacy_normal);
geometry::debug_randomize_point_order(pointcloud);
return pointcloud;
}
static Field<float> extract_non_negative_density(GeoNodeExecParams &params, const UString input)
{
const static mf::MultiFunction &max_fn = fn::multi_function::registry::lookup(
"max(float, float)"_ustr);
return Field<float>(FieldOperation::from(
max_fn, {params.extract_input<Field<float>>(input), fn::Field<float>(0.0f)}));
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"_ustr);
const Field<bool> selection = params.extract_input<Field<bool>>("Selection"_ustr);
const int seed = params.extract_input<int>("Seed"_ustr) * 5383843;
const NodeAttributeFilter attribute_filter = params.get_attribute_filter("Points"_ustr);
const bool use_legacy_normal = params.node().custom2 != 0;
AttributeOutputs attribute_outputs;
attribute_outputs.rotation_id = params.get_output_anonymous_attribute_id_if_needed(
"Rotation"_ustr);
attribute_outputs.normal_id = params.get_output_anonymous_attribute_id_if_needed(
"Normal"_ustr, bool(attribute_outputs.rotation_id));
lazy_threading::send_hint();
switch (GeometryNodeDistributePointsOnFacesMode(params.node().custom1)) {
case GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM: {
const Field<float> density = extract_non_negative_density(params, "Density"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
if (const Mesh *mesh = geometry_set.get_mesh()) {
PointCloud *pointcloud = create_points_random(*mesh,
selection,
density,
seed,
attribute_outputs,
attribute_filter,
use_legacy_normal);
geometry_set.replace_pointcloud(pointcloud);
}
geometry_set.keep_only(
{GeometryComponent::Type::PointCloud, GeometryComponent::Type::Edit});
});
break;
}
case GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON: {
const Field<float> factors = extract_non_negative_density(params, "Density Factor"_ustr);
const float minimum_distance = params.extract_input<float>("Distance Min"_ustr);
const float density_max = params.extract_input<float>("Density Max"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
if (const Mesh *mesh = geometry_set.get_mesh()) {
PointCloud *pointcloud = create_points_poisson_disk(*mesh,
selection,
factors,
minimum_distance,
density_max,
seed,
attribute_outputs,
attribute_filter,
use_legacy_normal);
geometry_set.replace_pointcloud(pointcloud);
geometry_set.keep_only(
{GeometryComponent::Type::PointCloud, GeometryComponent::Type::Edit});
}
});
break;
}
}
params.set_output("Points"_ustr, std::move(geometry_set));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeDistributePointsOnFaces"_ustr, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES);
ntype.ui_name = "Distribute Points on Faces";
ntype.ui_description = "Generate points spread out on the surface of a mesh";
ntype.enum_name_legacy = "DISTRIBUTE_POINTS_ON_FACES";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.default_width = bke::NodeWidth::_180;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
ntype.draw_buttons_ex = node_layout_ex;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_distribute_points_on_faces_cc

View File

@@ -0,0 +1,964 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <algorithm>
#include "BLI_task.hh"
#include "BKE_attribute_math.hh"
#include "BKE_mesh.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_dual_mesh_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Geometry>("Mesh"_ustr)
.supported_type(GeometryComponent::Type::Mesh)
.description("Mesh to compute the dual of");
b.add_output<decl::Geometry>("Dual Mesh"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Bool>("Keep Boundaries"_ustr)
.default_value(false)
.description(
"Keep non-manifold boundaries of the input mesh in place by avoiding the dual "
"transformation there");
}
enum class EdgeType : int8_t {
Loose = 0, /* No faces connected to it. */
Boundary = 1, /* An edge connected to exactly one face. */
Normal = 2, /* A normal edge (connected to two faces). */
NonManifold = 3, /* An edge connected to more than two faces. */
};
static EdgeType get_edge_type_with_added_neighbor(EdgeType old_type)
{
switch (old_type) {
case EdgeType::Loose:
return EdgeType::Boundary;
case EdgeType::Boundary:
return EdgeType::Normal;
case EdgeType::Normal:
case EdgeType::NonManifold:
return EdgeType::NonManifold;
}
BLI_assert_unreachable();
return EdgeType::Loose;
}
enum class VertexType : int8_t {
Loose = 0, /* Either no edges connected or only loose edges connected. */
Normal = 1, /* A normal vertex. */
Boundary = 2, /* A vertex on a boundary edge. */
NonManifold = 3, /* A vertex on a non-manifold edge. */
};
static VertexType get_vertex_type_with_added_neighbor(VertexType old_type)
{
switch (old_type) {
case VertexType::Loose:
return VertexType::Normal;
case VertexType::Normal:
return VertexType::Boundary;
case VertexType::Boundary:
case VertexType::NonManifold:
return VertexType::NonManifold;
}
BLI_assert_unreachable();
return VertexType::Loose;
}
/**
* Transfers the attributes from the original mesh to the new mesh using the following logic:
* - If the attribute was on the face domain it is now on the point domain, and this is true
* for all faces, so we can just copy these.
* - If the attribute was on the vertex domain there are three cases:
* - It was a 'bad' vertex so it is not in the dual mesh, and we can just ignore it
* - It was a normal vertex so it has a corresponding face in the dual mesh to which we can
* transfer.
* - It was a boundary vertex so it has a corresponding face, if keep_boundaries is true.
* Otherwise we can just ignore it.
* - If the attribute was on the edge domain we lookup for the new edges which edge it originated
* from using `new_to_old_edges_map`. We have to do it in this reverse order, because there can
* be more edges in the new mesh if keep boundaries is on.
* - We do the same thing for face corners as we do for edges.
*
* Some of the vertices (on the boundary) in the dual mesh don't come from faces, but from edges or
* vertices. For these the `boundary_vertex_to_relevant_face_map` is used, which maps them to the
* closest face.
*/
static void transfer_attributes(
const Span<VertexType> vertex_types,
const bool keep_boundaries,
const Span<int> new_to_old_edges_map,
const Span<int> new_to_old_face_corners_map,
const Span<std::pair<int, int>> boundary_vertex_to_relevant_face_map,
const AttributeFilter &attribute_filter,
const AttributeAccessor src_attributes,
MutableAttributeAccessor dst_attributes)
{
/* Retrieve all attributes except for position which is handled manually.
* Remove anonymous attributes that don't need to be propagated. */
Set<StringRefNull> names = src_attributes.all_names();
names.remove("position");
names.remove(".edge_verts");
names.remove(".corner_vert");
names.remove(".corner_edge");
names.remove("sharp_face");
names.remove_if([&](const StringRef name) { return attribute_filter.allow_skip(name); });
Array<int> new_face_to_old_vert;
const auto ensure_vert_map = [&]() {
if (!new_face_to_old_vert.is_empty()) {
return;
}
const int src_size = src_attributes.domain_size(bke::AttrDomain::Point);
const int dst_size = dst_attributes.domain_size(bke::AttrDomain::Face);
new_face_to_old_vert.reinitialize(dst_size);
if (keep_boundaries) {
int out_i = 0;
for (const int i : IndexRange(src_size)) {
if (ELEM(vertex_types[i], VertexType::Normal, VertexType::Boundary)) {
new_face_to_old_vert[out_i] = i;
out_i++;
}
}
}
else {
int out_i = 0;
for (const int i : IndexRange(src_size)) {
if (vertex_types[i] == VertexType::Normal) {
new_face_to_old_vert[out_i] = i;
out_i++;
}
}
}
};
IndexMaskMemory memory;
IndexMask boundary_vert_mask;
Array<int> boundary_vert_src_face;
const auto ensure_face_map = [&]() {
if (!boundary_vert_src_face.is_empty()) {
return;
}
Array<int> boundary_verts(boundary_vertex_to_relevant_face_map.size());
for (const int i : boundary_vertex_to_relevant_face_map.index_range()) {
boundary_verts[i] = boundary_vertex_to_relevant_face_map[i].first;
}
boundary_vert_mask = IndexMask::from_indices(boundary_verts.as_span(), memory);
boundary_vert_src_face.reinitialize(boundary_vert_mask.min_array_size());
boundary_vert_mask.foreach_index(
[&](const int i, const int pos) {
boundary_vert_src_face[i] = boundary_vertex_to_relevant_face_map[pos].second;
},
exec_mode::grain_size(8196));
};
for (const StringRef name : names) {
GAttributeReader src = src_attributes.lookup(name);
AttrDomain out_domain;
if (src.domain == AttrDomain::Face) {
out_domain = AttrDomain::Point;
}
else if (src.domain == AttrDomain::Point) {
out_domain = AttrDomain::Face;
}
else {
/* Edges and Face Corners. */
out_domain = src.domain;
}
const bke::AttrType data_type = bke::cpp_type_to_attribute_type(src.varray.type());
const CommonVArrayInfo info = src.varray.common_info();
if (info.type == CommonVArrayInfo::Type::Single) {
const GPointer value(src.varray.type(), info.data);
if (dst_attributes.add(name, out_domain, data_type, bke::AttributeInitValue(value))) {
continue;
}
}
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
name, out_domain, data_type);
if (!dst) {
continue;
}
switch (src.domain) {
case AttrDomain::Point:
ensure_vert_map();
bke::attribute_math::gather(*src, new_face_to_old_vert, dst.span);
break;
case AttrDomain::Edge:
bke::attribute_math::gather(*src, new_to_old_edges_map, dst.span);
break;
case AttrDomain::Face: {
const GVArraySpan src_span(*src);
dst.span.take_front(src_span.size()).copy_from(src_span);
if (keep_boundaries) {
ensure_face_map();
bke::attribute_math::gather(
src.varray, boundary_vert_src_face, boundary_vert_mask, dst.span);
}
break;
}
case AttrDomain::Corner:
bke::attribute_math::gather(*src, new_to_old_face_corners_map, dst.span);
break;
default:
BLI_assert_unreachable();
}
dst.finish();
}
}
/**
* Calculates the boundaries of the mesh. Boundary faces are not computed since we don't need
* them later on. We use the following definitions:
* - An edge is on a boundary if it is connected to only one face.
* - A vertex is on a boundary if it is on an edge on a boundary.
*/
static void calc_boundaries(const Mesh &mesh,
MutableSpan<VertexType> r_vertex_types,
MutableSpan<EdgeType> r_edge_types)
{
BLI_assert(r_vertex_types.size() == mesh.verts_num);
BLI_assert(r_edge_types.size() == mesh.edges_num);
const Span<int2> edges = mesh.edges();
const OffsetIndices faces = mesh.faces();
const Span<int> corner_edges = mesh.corner_edges();
r_vertex_types.fill(VertexType::Loose);
r_edge_types.fill(EdgeType::Loose);
/* Add up the number of faces connected to each edge. */
for (const int i : IndexRange(mesh.faces_num)) {
for (const int edge_i : corner_edges.slice(faces[i])) {
r_edge_types[edge_i] = get_edge_type_with_added_neighbor(r_edge_types[edge_i]);
}
}
/* Update vertices. */
for (const int i : IndexRange(mesh.edges_num)) {
const EdgeType edge_type = r_edge_types[i];
if (edge_type == EdgeType::Loose) {
continue;
}
const int2 &edge = edges[i];
if (edge_type == EdgeType::Boundary) {
r_vertex_types[edge[0]] = get_vertex_type_with_added_neighbor(r_vertex_types[edge[0]]);
r_vertex_types[edge[1]] = get_vertex_type_with_added_neighbor(r_vertex_types[edge[1]]);
}
else if (edge_type >= EdgeType::NonManifold) {
r_vertex_types[edge[0]] = VertexType::NonManifold;
r_vertex_types[edge[1]] = VertexType::NonManifold;
}
}
/* Normal verts are on a normal edge, and not on boundary edges or non-manifold edges. */
for (const int i : IndexRange(mesh.edges_num)) {
const EdgeType edge_type = r_edge_types[i];
if (edge_type == EdgeType::Normal) {
const int2 &edge = edges[i];
if (r_vertex_types[edge[0]] == VertexType::Loose) {
r_vertex_types[edge[0]] = VertexType::Normal;
}
if (r_vertex_types[edge[1]] == VertexType::Loose) {
r_vertex_types[edge[1]] = VertexType::Normal;
}
}
}
}
/**
* Sorts the faces connected to the given vertex based on face adjacency. The ordering is
* so such that the normals point in the same way as the original mesh. If the vertex is a
* boundary vertex, the first and last face have a boundary edge connected to the vertex. The
* `r_shared_edges` array at index i is set to the index of the shared edge between the i-th and
* `(i+1)-th` sorted face. Similarly the `r_sorted_corners` array at index i is set to the
* corner in the i-th sorted face. If the faces couldn't be sorted, `false` is returned.
*
* How the faces are sorted (see diagrams below):
* (For this explanation we'll assume all faces are oriented clockwise)
* (The vertex whose connected faces we need to sort is "v0")
*
* \code{.unparsed}
* Normal case: Boundary Vertex case:
* v1 ----- v2 ----- v3 | | |
* | f3 | f0 | v2 ---- v4 --------- v3---
* | | | | / ,-' |
* v8 ----- v0 ----- v4 | f0 / f1 ,-' |
* | f2 | f1 | | / ,-' |
* | | | | / ,-' |
* v7 ----- v6 ----- v5 | / ,-' f2 |
* | /,-' |
* v0 ------------------ v1---
* \endcode
*
* - First we get the two corners of each face that have an edge which contains v0. A corner is
* simply a vertex followed by an edge. In this case for the face "f0" for example, we'd end up
* with the corners (v: v4, e: v4<->v0) and (v: v0, e: v0<->v2). Note that if the face was
* oriented counter-clockwise we'd end up with the corners (v: v0, e: v0<->v4) and (v: v2, e:
* v0<->v2) instead.
* - Then we need to choose one face as our first. If "v0" is not on a boundary we can just
* choose any face. If it is on a boundary some more care needs to be taken. Here we need to
* pick a face which lies on the boundary (in the diagram either f0 or f2). To choose between
* the two we need the next step.
* - In the normal case we use this face to set `shared_edge_i` which indicates the index of the
* shared edge between this face and the next one. There are two possible choices: v0<->v4 and
* v2<->v0. To choose we look at the corners. Since the edge v0<->v2 lies on the corner which has
* v0, we set `shared_edge_i` to the other edge (v0<->v4), such that the next face will be "f1"
* which is the next face in clockwise order.
* - In the boundary vertex case, we do something similar, but we are also forced to choose the
* edge which is not on the boundary. If this doesn't line up with orientation of the face, we
* know we'll need to choose the other boundary face as our first face. If the orientations
* don't line up there as well, it means that the mesh normals are not consistent, and we just
* have to force an orientation for ourselves. (Imagine if f0 is oriented counter-clockwise and
* f2 is oriented clockwise for example)
* - Next comes a loop where we look at the other faces and find the one which has the shared
* edge. Then we set the next shared edge to the other edge on the face connected to "v0", and
* continue. Because of the way we've chosen the first shared edge the order of the faces will
* have the same orientation as that of the first face.
* (In this case we'd have f0 -> f1 -> f2 -> f3 which also goes around clockwise).
* - Every time we determine a shared edge, we can also add a corner to `r_sorted_corners`. This
* will simply be the corner which doesn't contain the shared edge.
* - Finally if we are in the normal case we also need to add the last "shared edge" to close the
* loop.
*/
static bool sort_vertex_faces(const Span<int2> edges,
const OffsetIndices<int> faces,
const Span<int> corner_verts,
const Span<int> corner_edges,
const int vertex_index,
const bool boundary_vertex,
const Span<EdgeType> edge_types,
MutableSpan<int> connected_faces,
MutableSpan<int> r_shared_edges,
MutableSpan<int> r_sorted_corners)
{
if (connected_faces.size() <= 2 && (!boundary_vertex || connected_faces.is_empty())) {
return true;
}
/* For each face store the two corners whose edge contains the vertex. */
Array<std::pair<int, int>, 16> face_vertex_corners(connected_faces.size());
for (const int i : connected_faces.index_range()) {
bool first_edge_done = false;
for (const int corner : faces[connected_faces[i]]) {
const int edge = corner_edges[corner];
if (edges[edge][0] == vertex_index || edges[edge][1] == vertex_index) {
if (!first_edge_done) {
face_vertex_corners[i].first = corner;
first_edge_done = true;
}
else {
face_vertex_corners[i].second = corner;
break;
}
}
}
}
int shared_edge_i = -1;
/* Determine first face and orientation. For now the orientation of the whole loop depends
* on the one face we chose as first. It's probably not worth it to check every face in
* the loop to determine the 'average' orientation. */
if (boundary_vertex) {
/* Our first face needs to be one which has a boundary edge. */
for (const int i : connected_faces.index_range()) {
const int corner_1 = face_vertex_corners[i].first;
const int corner_2 = face_vertex_corners[i].second;
if (edge_types[corner_edges[corner_1]] == EdgeType::Boundary &&
corner_verts[corner_1] == vertex_index)
{
shared_edge_i = corner_edges[corner_2];
r_sorted_corners[0] = face_vertex_corners[i].first;
std::swap(connected_faces[i], connected_faces[0]);
std::swap(face_vertex_corners[i], face_vertex_corners[0]);
break;
}
if (edge_types[corner_edges[corner_2]] == EdgeType::Boundary &&
corner_verts[corner_2] == vertex_index)
{
shared_edge_i = corner_edges[corner_1];
r_sorted_corners[0] = face_vertex_corners[i].second;
std::swap(connected_faces[i], connected_faces[0]);
std::swap(face_vertex_corners[i], face_vertex_corners[0]);
break;
}
}
if (shared_edge_i == -1) {
/* The rotation is inconsistent between the two faces on the boundary. Just choose one
* of the face's orientation. */
for (const int i : connected_faces.index_range()) {
const int corner_1 = face_vertex_corners[i].first;
const int corner_2 = face_vertex_corners[i].second;
if (edge_types[corner_edges[corner_1]] == EdgeType::Boundary) {
shared_edge_i = corner_edges[corner_2];
r_sorted_corners[0] = face_vertex_corners[i].first;
std::swap(connected_faces[i], connected_faces[0]);
std::swap(face_vertex_corners[i], face_vertex_corners[0]);
break;
}
if (edge_types[corner_edges[corner_2]] == EdgeType::Boundary) {
shared_edge_i = corner_edges[corner_1];
r_sorted_corners[0] = face_vertex_corners[i].second;
std::swap(connected_faces[i], connected_faces[0]);
std::swap(face_vertex_corners[i], face_vertex_corners[0]);
break;
}
}
}
}
else {
/* Any face can be the first. Just need to check the orientation. */
const int corner_1 = face_vertex_corners.first().first;
const int corner_2 = face_vertex_corners.first().second;
if (corner_verts[corner_1] == vertex_index) {
shared_edge_i = corner_edges[corner_2];
r_sorted_corners[0] = face_vertex_corners[0].first;
}
else {
r_sorted_corners[0] = face_vertex_corners[0].second;
shared_edge_i = corner_edges[corner_1];
}
}
BLI_assert(shared_edge_i != -1);
for (const int i : IndexRange(connected_faces.size() - 1)) {
r_shared_edges[i] = shared_edge_i;
/* Look at the other faces to see if it has this shared edge. */
int j = i + 1;
for (; j < connected_faces.size(); ++j) {
const int corner_1 = face_vertex_corners[j].first;
const int corner_2 = face_vertex_corners[j].second;
if (corner_edges[corner_1] == shared_edge_i) {
r_sorted_corners[i + 1] = face_vertex_corners[j].first;
shared_edge_i = corner_edges[corner_2];
break;
}
if (corner_edges[corner_2] == shared_edge_i) {
r_sorted_corners[i + 1] = face_vertex_corners[j].second;
shared_edge_i = corner_edges[corner_1];
break;
}
}
if (j == connected_faces.size()) {
/* The vertex is not manifold because the faces around the vertex don't form a loop, and
* hence can't be sorted. */
return false;
}
std::swap(connected_faces[i + 1], connected_faces[j]);
std::swap(face_vertex_corners[i + 1], face_vertex_corners[j]);
}
if (!boundary_vertex) {
/* Shared edge between first and last face. */
r_shared_edges.last() = shared_edge_i;
}
return true;
}
/**
* Get the edge on the face that contains the given vertex and is a boundary edge.
*/
static void boundary_edge_on_face(const Span<int2> edges,
const Span<int> face_edges,
const int vertex_index,
const Span<EdgeType> edge_types,
int &r_edge)
{
for (const int edge_i : face_edges) {
if (edge_types[edge_i] == EdgeType::Boundary) {
const int2 &edge = edges[edge_i];
if (edge[0] == vertex_index || edge[1] == vertex_index) {
r_edge = edge_i;
return;
}
}
}
}
/**
* Get the two edges on the face that contain the given vertex and are boundary edges. The
* orientation of the face is taken into account.
*/
static void boundary_edges_on_face(const IndexRange face,
const Span<int2> edges,
const Span<int> corner_verts,
const Span<int> corner_edges,
const int vertex_index,
const Span<EdgeType> edge_types,
int &r_edge1,
int &r_edge2)
{
bool edge1_done = false;
/* This is set to true if the order in which we encounter the two edges is inconsistent with the
* orientation of the face. */
bool needs_swap = false;
for (const int corner : face) {
const int edge_i = corner_edges[corner];
if (edge_types[edge_i] == EdgeType::Boundary) {
const int2 &edge = edges[edge_i];
if (edge[0] == vertex_index || edge[1] == vertex_index) {
if (edge1_done) {
if (needs_swap) {
r_edge2 = r_edge1;
r_edge1 = edge_i;
}
else {
r_edge2 = edge_i;
}
return;
}
r_edge1 = edge_i;
edge1_done = true;
if (corner_verts[corner] == vertex_index) {
needs_swap = true;
}
}
}
}
}
static void add_edge(const int old_edge_i,
const int v1,
const int v2,
Vector<int> &new_to_old_edges_map,
Vector<int2> &new_edges,
Vector<int> &corner_edges)
{
const int new_edge_i = new_edges.size();
new_to_old_edges_map.append(old_edge_i);
new_edges.append({v1, v2});
corner_edges.append(new_edge_i);
}
/* Returns true if the vertex is connected only to the two faces and is not on the boundary. */
static bool vertex_needs_dissolving(const int vertex,
const int first_face_index,
const int second_face_index,
const Span<VertexType> vertex_types,
const GroupedSpan<int> vert_to_face_map)
{
/* Order is guaranteed to be the same because 2face verts that are not on the boundary are
* ignored in `sort_vertex_faces`. */
return (vertex_types[vertex] != VertexType::Boundary && vert_to_face_map[vertex].size() == 2 &&
vert_to_face_map[vertex][0] == first_face_index &&
vert_to_face_map[vertex][1] == second_face_index);
}
/**
* Finds 'normal' vertices which are connected to only two faces and marks them to not be
* used in the data-structures derived from the mesh. For each pair of faces which has such a
* vertex, an edge is created for the dual mesh between the centers of those two faces. All
* edges in the input mesh which contain such a vertex are marked as 'done' to prevent duplicate
* edges being created. (See #94144)
*/
static void dissolve_redundant_verts(const Span<int2> edges,
const OffsetIndices<int> faces,
const Span<int> corner_edges,
const GroupedSpan<int> vert_to_face_map,
MutableSpan<VertexType> vertex_types,
MutableSpan<int> old_to_new_edges_map,
Vector<int2> &new_edges,
Vector<int> &new_to_old_edges_map)
{
const int vertex_num = vertex_types.size();
for (const int vert_i : IndexRange(vertex_num)) {
if (vert_to_face_map[vert_i].size() != 2 || vertex_types[vert_i] != VertexType::Normal) {
continue;
}
const int first_face_index = vert_to_face_map[vert_i][0];
const int second_face_index = vert_to_face_map[vert_i][1];
const int new_edge_index = new_edges.size();
bool edge_created = false;
for (const int edge_i : corner_edges.slice(faces[first_face_index])) {
const int2 &edge = edges[edge_i];
bool mark_edge = false;
if (vertex_needs_dissolving(
edge[0], first_face_index, second_face_index, vertex_types, vert_to_face_map))
{
/* This vertex is now 'removed' and should be ignored elsewhere. */
vertex_types[edge[0]] = VertexType::Loose;
mark_edge = true;
}
if (vertex_needs_dissolving(
edge[1], first_face_index, second_face_index, vertex_types, vert_to_face_map))
{
/* This vertex is now 'removed' and should be ignored elsewhere. */
vertex_types[edge[1]] = VertexType::Loose;
mark_edge = true;
}
if (mark_edge) {
if (!edge_created) {
/* The vertex indices in the dual mesh are the face indices of the input mesh. */
new_to_old_edges_map.append(edge_i);
new_edges.append({first_face_index, second_face_index});
edge_created = true;
}
old_to_new_edges_map[edge_i] = new_edge_index;
}
}
}
}
/**
* Calculate the barycentric dual of a mesh. The dual is only "dual" in terms of connectivity,
* i.e. applying the function twice will give the same vertices, edges, and faces, but not the
* same positions. When the option "Keep Boundaries" is selected the connectivity is no
* longer dual.
*
* For the dual mesh of a manifold input mesh:
* - The vertices are at the centers of the faces of the input mesh.
* - The edges connect the two vertices created from the two faces next to the edge in the input
* mesh.
* - The faces are at the vertices of the input mesh.
*
* Some special cases are needed for boundaries and non-manifold geometry.
*/
static Mesh *calc_dual_mesh(const Mesh &src_mesh,
const bool keep_boundaries,
const AttributeFilter &attribute_filter)
{
const Span<float3> src_positions = src_mesh.vert_positions();
const Span<int2> src_edges = src_mesh.edges();
const OffsetIndices src_faces = src_mesh.faces();
const Span<int> src_corner_verts = src_mesh.corner_verts();
const Span<int> src_corner_edges = src_mesh.corner_edges();
Array<VertexType> vertex_types(src_mesh.verts_num);
Array<EdgeType> edge_types(src_mesh.edges_num);
calc_boundaries(src_mesh, vertex_types, edge_types);
/* Stores the indices of the faces connected to the vertex. Because the faces are looped
* over in order of their indices, the face's indices will be sorted in ascending order.
* (This can change once they are sorted using `sort_vertex_faces`). */
Array<int> vert_to_face_indices = src_mesh.vert_to_face_map().data;
const OffsetIndices<int> vert_to_face_offsets = src_mesh.vert_to_face_map().offsets;
Array<Array<int>> vertex_shared_edges(src_mesh.verts_num);
Array<Array<int>> vertex_corners(src_mesh.verts_num);
threading::parallel_for(src_positions.index_range(), 512, [&](IndexRange range) {
for (const int i : range) {
if (vertex_types[i] == VertexType::Loose || vertex_types[i] >= VertexType::NonManifold ||
(!keep_boundaries && vertex_types[i] == VertexType::Boundary))
{
/* Bad vertex that we can't work with. */
continue;
}
MutableSpan<int> corner_indices = vert_to_face_indices.as_mutable_span().slice(
vert_to_face_offsets[i]);
Array<int> sorted_corners(corner_indices.size());
bool vertex_ok = true;
if (vertex_types[i] == VertexType::Normal) {
Array<int> shared_edges(corner_indices.size());
vertex_ok = sort_vertex_faces(src_edges,
src_faces,
src_corner_verts,
src_corner_edges,
i,
false,
edge_types,
corner_indices,
shared_edges,
sorted_corners);
vertex_shared_edges[i] = std::move(shared_edges);
}
else {
Array<int> shared_edges(corner_indices.size() - 1);
vertex_ok = sort_vertex_faces(src_edges,
src_faces,
src_corner_verts,
src_corner_edges,
i,
true,
edge_types,
corner_indices,
shared_edges,
sorted_corners);
vertex_shared_edges[i] = std::move(shared_edges);
}
if (!vertex_ok) {
/* The sorting failed which means that the vertex is non-manifold and should be ignored
* further on. */
vertex_types[i] = VertexType::NonManifold;
continue;
}
vertex_corners[i] = std::move(sorted_corners);
}
});
const GroupedSpan<int> vert_to_face_map(vert_to_face_offsets, vert_to_face_indices);
Vector<float3> vert_positions(src_mesh.faces_num);
for (const int i : src_faces.index_range()) {
const IndexRange face = src_faces[i];
vert_positions[i] = bke::mesh::face_center_calc(src_positions, src_corner_verts.slice(face));
}
Array<int> boundary_edge_midpoint_index;
if (keep_boundaries) {
/* Only initialize when we actually need it. */
boundary_edge_midpoint_index.reinitialize(src_mesh.edges_num);
/* We need to add vertices at the centers of boundary edges. */
for (const int i : IndexRange(src_mesh.edges_num)) {
if (edge_types[i] == EdgeType::Boundary) {
const int2 &edge = src_edges[i];
const float3 mid = math::midpoint(src_positions[edge[0]], src_positions[edge[1]]);
boundary_edge_midpoint_index[i] = vert_positions.size();
vert_positions.append(mid);
}
}
}
Vector<int> face_sizes;
Vector<int> corner_verts;
Vector<int> corner_edges;
Vector<int2> new_edges;
/* These are used to transfer attributes. */
Vector<int> new_to_old_face_corners_map;
Vector<int> new_to_old_edges_map;
/* Stores the index of the vertex in the dual and the face it should get the attribute from. */
Vector<std::pair<int, int>> boundary_vertex_to_relevant_face_map;
/* Since each edge in the dual (except the ones created with keep boundaries) comes from
* exactly one edge in the original, we can use this array to keep track of whether it still
* needs to be created or not. If it's not -1 it gives the index in `new_edges` of the dual
* edge. The edges coming from preserving the boundaries only get added once anyway, so we
* don't need a hash-map for that. */
Array<int> old_to_new_edges_map(src_mesh.edges_num);
old_to_new_edges_map.fill(-1);
/* This is necessary to prevent duplicate edges from being created, but will likely not do
* anything for most meshes. */
dissolve_redundant_verts(src_edges,
src_faces,
src_corner_edges,
vert_to_face_map,
vertex_types,
old_to_new_edges_map,
new_edges,
new_to_old_edges_map);
for (const int i : IndexRange(src_mesh.verts_num)) {
if (vertex_types[i] == VertexType::Loose || vertex_types[i] >= VertexType::NonManifold ||
(!keep_boundaries && vertex_types[i] == VertexType::Boundary))
{
/* Bad vertex that we can't work with. */
continue;
}
Vector<int> corner_indices = vert_to_face_map[i];
Span<int> shared_edges = vertex_shared_edges[i];
Span<int> sorted_corners = vertex_corners[i];
if (vertex_types[i] == VertexType::Normal) {
if (corner_indices.size() <= 2) {
/* We can't make a face from 2 vertices. */
continue;
}
/* Add edges in the loop. */
for (const int i : shared_edges.index_range()) {
const int old_edge_i = shared_edges[i];
if (old_to_new_edges_map[old_edge_i] == -1) {
/* This edge has not been created yet. */
new_to_old_edges_map.append(old_edge_i);
old_to_new_edges_map[old_edge_i] = new_edges.size();
new_edges.append({corner_indices[i], corner_indices[(i + 1) % corner_indices.size()]});
}
corner_edges.append(old_to_new_edges_map[old_edge_i]);
}
new_to_old_face_corners_map.extend(sorted_corners);
}
else {
/**
* The code handles boundary vertices like the vertex marked "V" in the diagram below.
* The first thing that happens is ordering the faces f1,f2 and f3 (stored in
* corner_indices), together with their shared edges e3 and e4 (which get stored in
* shared_edges). The ordering could end up being clockwise or counterclockwise, for this
* we'll assume that the ordering f1->f2->f3 is chosen. After that we add the edges in
* between the faces, in this case the edges f1--f2, and f2--f3. Now we need to merge
* these with the boundary edges e1 and e2. To do this we create an edge from f3 to the
* midpoint of e2 (computed in a previous step), from this midpoint to V, from V to the
* midpoint of e1 and from the midpoint of e1 to f1.
*
* \code{.unparsed}
* | | | | | |
* v2 ---- v3 --------- v4--- v2 ---- v3 -------- v4---
* | f3 / ,-' | | / ,-'|
* | / f2 ,-' | | / ,-' |
* e2 | /e3 ,-' e4 | ====> M1-f3-/--f2-.,-' |
* | / ,-' | ====> | / ,-'\ |
* | / ,-' f1 | | / ,-' f1 |
* | /,-' | | /,-' | |
* V-------------------- v5--- V------------M2----- v5---
* \endcode
*/
/* Add the edges in between the faces. */
for (const int i : shared_edges.index_range()) {
const int old_edge_i = shared_edges[i];
if (old_to_new_edges_map[old_edge_i] == -1) {
/* This edge has not been created yet. */
new_to_old_edges_map.append(old_edge_i);
old_to_new_edges_map[old_edge_i] = new_edges.size();
new_edges.append({corner_indices[i], corner_indices[i + 1]});
}
corner_edges.append(old_to_new_edges_map[old_edge_i]);
}
new_to_old_face_corners_map.extend(sorted_corners);
/* Add the vertex and the midpoints of the two boundary edges to the loop. */
/* Get the boundary edges. */
int edge1;
int edge2;
if (corner_indices.size() >= 2) {
/* The first boundary edge is at the end of the chain of faces. */
boundary_edge_on_face(src_edges,
src_corner_edges.slice(src_faces[corner_indices.last()]),
i,
edge_types,
edge1);
boundary_edge_on_face(src_edges,
src_corner_edges.slice(src_faces[corner_indices.first()]),
i,
edge_types,
edge2);
}
else {
/* If there is only one face both edges are in that face. */
boundary_edges_on_face(src_faces[corner_indices[0]],
src_edges,
src_corner_verts,
src_corner_edges,
i,
edge_types,
edge1,
edge2);
}
const int last_face_center = corner_indices.last();
corner_indices.append(boundary_edge_midpoint_index[edge1]);
new_to_old_face_corners_map.append(sorted_corners.last());
const int first_midpoint = corner_indices.last();
if (old_to_new_edges_map[edge1] == -1) {
add_edge(edge1,
last_face_center,
first_midpoint,
new_to_old_edges_map,
new_edges,
corner_edges);
old_to_new_edges_map[edge1] = new_edges.size() - 1;
boundary_vertex_to_relevant_face_map.append(std::pair(first_midpoint, last_face_center));
}
else {
corner_edges.append(old_to_new_edges_map[edge1]);
}
corner_indices.append(vert_positions.size());
/* This is sort of arbitrary, but interpolating would be a lot harder to do. */
new_to_old_face_corners_map.append(sorted_corners.first());
boundary_vertex_to_relevant_face_map.append(
std::pair(corner_indices.last(), last_face_center));
vert_positions.append(src_positions[i]);
const int boundary_vertex = corner_indices.last();
add_edge(
edge1, first_midpoint, boundary_vertex, new_to_old_edges_map, new_edges, corner_edges);
corner_indices.append(boundary_edge_midpoint_index[edge2]);
new_to_old_face_corners_map.append(sorted_corners.first());
const int second_midpoint = corner_indices.last();
add_edge(
edge2, boundary_vertex, second_midpoint, new_to_old_edges_map, new_edges, corner_edges);
if (old_to_new_edges_map[edge2] == -1) {
const int first_face_center = corner_indices.first();
add_edge(edge2,
second_midpoint,
first_face_center,
new_to_old_edges_map,
new_edges,
corner_edges);
old_to_new_edges_map[edge2] = new_edges.size() - 1;
boundary_vertex_to_relevant_face_map.append(std::pair(second_midpoint, first_face_center));
}
else {
corner_edges.append(old_to_new_edges_map[edge2]);
}
}
face_sizes.append(corner_indices.size());
corner_verts.extend(corner_indices);
}
Mesh *mesh_out = BKE_mesh_new_nomain(
vert_positions.size(), new_edges.size(), face_sizes.size(), corner_verts.size());
bke::mesh_smooth_set(*mesh_out, false);
std::ranges::sort(boundary_vertex_to_relevant_face_map,
[](const auto &a, const auto &b) { return a.first < b.first; });
transfer_attributes(vertex_types,
keep_boundaries,
new_to_old_edges_map,
new_to_old_face_corners_map,
boundary_vertex_to_relevant_face_map,
attribute_filter,
src_mesh.attributes(),
mesh_out->attributes_for_write());
mesh_out->vert_positions_for_write().copy_from(vert_positions);
mesh_out->edges_for_write().copy_from(new_edges);
if (mesh_out->faces_num > 0) {
MutableSpan<int> dst_face_offsets = mesh_out->face_offsets_for_write();
dst_face_offsets.drop_back(1).copy_from(face_sizes);
offset_indices::accumulate_counts_to_offsets(dst_face_offsets);
}
mesh_out->corner_verts_for_write().copy_from(corner_verts);
mesh_out->corner_edges_for_write().copy_from(corner_edges);
return mesh_out;
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"_ustr);
const bool keep_boundaries = params.extract_input<bool>("Keep Boundaries"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
if (const Mesh *mesh = geometry_set.get_mesh()) {
Mesh *new_mesh = calc_dual_mesh(
*mesh, keep_boundaries, params.get_attribute_filter("Dual Mesh"_ustr));
geometry::debug_randomize_mesh_order(new_mesh);
geometry_set.replace_mesh(new_mesh);
}
});
params.set_output("Dual Mesh"_ustr, std::move(geometry_set));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeDualMesh"_ustr, GEO_NODE_DUAL_MESH);
ntype.ui_name = "Dual Mesh";
ntype.ui_description = "Convert Faces into vertices and vertices into faces";
ntype.enum_name_legacy = "DUAL_MESH";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_dual_mesh_cc

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "DNA_mesh_types.h"
#include "GEO_foreach_geometry.hh"
#include "GEO_mesh_to_curve.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_edge_paths_to_curves_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Mesh"_ustr)
.supported_type(GeometryComponent::Type::Mesh)
.description("Edges to convert to curves");
b.add_input<decl::Bool>("Start Vertices"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field();
b.add_input<decl::Int>("Next Vertex Index"_ustr)
.default_value(-1)
.hide_value()
.evaluated_geometry_field();
b.add_output<decl::Geometry>("Curves"_ustr).propagate_all_geometry();
}
static Curves *edge_paths_to_curves_convert(const Mesh &mesh,
const IndexMask &start_verts_mask,
const Span<int> next_indices,
const AttributeFilter &attribute_filter)
{
Vector<int> vert_indices;
Vector<int> curve_offsets;
Array<bool> visited(mesh.verts_num, false);
start_verts_mask.foreach_index([&](const int first_vert) {
const int second_vert = next_indices[first_vert];
if (first_vert == second_vert) {
return;
}
if (second_vert < 0 || second_vert >= mesh.verts_num) {
return;
}
curve_offsets.append(vert_indices.size());
/* Iterate through path defined by #next_indices. */
int current_vert = first_vert;
while (!visited[current_vert]) {
visited[current_vert] = true;
vert_indices.append(current_vert);
const int next_vert = next_indices[current_vert];
if (next_vert < 0 || next_vert >= mesh.verts_num) {
break;
}
current_vert = next_vert;
}
/* Reset visited status. */
const int points_in_curve_num = vert_indices.size() - curve_offsets.last();
for (const int vert_in_curve : vert_indices.as_span().take_back(points_in_curve_num)) {
visited[vert_in_curve] = false;
}
});
if (vert_indices.is_empty()) {
return nullptr;
}
Curves *curves_id = bke::curves_new_nomain(geometry::create_curve_from_vert_indices(
mesh.attributes(), vert_indices, curve_offsets, IndexRange(0), attribute_filter));
return curves_id;
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
const Mesh *mesh = geometry_set.get_mesh();
if (mesh == nullptr) {
geometry_set.keep_only({GeometryComponent::Type::Edit});
return;
}
const bke::MeshFieldContext context{*mesh, AttrDomain::Point};
fn::FieldEvaluator evaluator{context, mesh->verts_num};
evaluator.add(params.get_input<Field<int>>("Next Vertex Index"_ustr));
evaluator.add(params.get_input<Field<bool>>("Start Vertices"_ustr));
evaluator.evaluate();
const VArraySpan<int> next_vert = evaluator.get_evaluated<int>(0);
IndexMask start_verts = evaluator.get_evaluated_as_mask(1);
if (start_verts.is_empty()) {
geometry_set.keep_only({GeometryComponent::Type::Edit});
return;
}
geometry_set.replace_curves(edge_paths_to_curves_convert(
*mesh, start_verts, next_vert, params.get_attribute_filter("Curves"_ustr)));
geometry_set.keep_only({GeometryComponent::Type::Curve, GeometryComponent::Type::Edit});
});
params.set_output("Curves"_ustr, std::move(geometry_set));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeEdgePathsToCurves"_ustr, GEO_NODE_EDGE_PATHS_TO_CURVES);
ntype.ui_name = "Edge Paths to Curves";
ntype.ui_description = "Output curves following paths across mesh edges";
ntype.enum_name_legacy = "EDGE_PATHS_TO_CURVES";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.default_width = bke::NodeWidth::_160;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_edge_paths_to_curves_cc

View File

@@ -0,0 +1,145 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_mesh_types.h"
#include "BLI_task.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_edge_paths_to_selection_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Bool>("Start Vertices"_ustr)
.default_value(true)
.hide_value()
.structure_type(StructureType::Field);
b.add_input<decl::Int>("Next Vertex Index"_ustr)
.default_value(-1)
.hide_value()
.structure_type(StructureType::Field);
b.add_output<decl::Bool>("Selection"_ustr)
.structure_type(StructureType::Field)
.propagate_references();
}
static void edge_paths_to_selection(const Mesh &src_mesh,
const IndexMask &start_selection,
const Span<int> next_indices,
MutableSpan<bool> r_edge_selection)
{
Array<bool> vert_selection(src_mesh.verts_num, false);
const IndexRange vert_range(src_mesh.verts_num);
start_selection.foreach_index(
[&](const int start_vert) {
/* If vertex is selected, all next is already selected too. */
for (int current_vert = start_vert; !vert_selection[current_vert];
current_vert = next_indices[current_vert])
{
if (UNLIKELY(!vert_range.contains(current_vert))) {
break;
}
vert_selection[current_vert] = true;
}
},
exec_mode::grain_size(2048));
const Span<int2> edges = src_mesh.edges();
threading::parallel_for(edges.index_range(), 4096, [&](const IndexRange range) {
for (const int i : range) {
const int2 edge = edges[i];
if (!(vert_selection[edge[0]] && vert_selection[edge[1]])) {
continue;
}
if (edge[0] == next_indices[edge[1]] || edge[1] == next_indices[edge[0]]) {
r_edge_selection[i] = true;
}
}
});
}
class PathToEdgeSelectionFieldInput final : public bke::MeshFieldInput {
private:
Field<bool> start_vertices_;
Field<int> next_vertex_;
public:
PathToEdgeSelectionFieldInput(Field<bool> start_verts, Field<int> next_vertex)
: bke::MeshFieldInput(CPPType::get<bool>(), "Edge Selection"),
start_vertices_(start_verts),
next_vertex_(next_vertex)
{
}
GVArray get_varray_for_context(const Mesh &mesh,
const AttrDomain domain,
const IndexMask & /*mask*/) const final
{
const bke::MeshFieldContext context{mesh, AttrDomain::Point};
fn::FieldEvaluator evaluator{context, mesh.verts_num};
evaluator.add(next_vertex_);
evaluator.add(start_vertices_);
evaluator.evaluate();
const VArraySpan<int> next_vert = evaluator.get_evaluated<int>(0);
const IndexMask start_verts = evaluator.get_evaluated_as_mask(1);
if (start_verts.is_empty()) {
return {};
}
Array<bool> selection(mesh.edges_num, false);
edge_paths_to_selection(mesh, start_verts, next_vert, selection);
return mesh.attributes().adapt_domain<bool>(
VArray<bool>::from_container(std::move(selection)), AttrDomain::Edge, domain);
}
void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const override
{
fn(start_vertices_);
fn(next_vertex_);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(start_vertices_));
hash.add(deep_hash_cache.ensure(next_vertex_));
}
std::optional<AttrDomain> preferred_domain(const Mesh & /*mesh*/) const override
{
return AttrDomain::Edge;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
Field<bool> start_verts = params.extract_input<Field<bool>>("Start Vertices"_ustr);
Field<int> next_vertex = params.extract_input<Field<int>>("Next Vertex Index"_ustr);
params.set_output(
"Selection"_ustr,
Field<bool>::from_input<PathToEdgeSelectionFieldInput>(start_verts, next_vertex));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeEdgePathsToSelection"_ustr, GEO_NODE_EDGE_PATHS_TO_SELECTION);
ntype.ui_name = "Edge Paths to Selection";
ntype.ui_description = "Output a selection of edges by following paths across mesh edges";
ntype.enum_name_legacy = "EDGE_PATHS_TO_SELECTION";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.default_width = bke::NodeWidth::_160;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_edge_paths_to_selection_cc

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_mesh_types.h"
#include "GEO_foreach_geometry.hh"
#include "GEO_mesh_split_edges.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_edge_split_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Geometry>("Mesh"_ustr)
.supported_type(GeometryComponent::Type::Mesh)
.description("Mesh whose edges to split");
b.add_output<decl::Geometry>("Mesh"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field();
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"_ustr);
const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
if (const Mesh *mesh = geometry_set.get_mesh()) {
const bke::MeshFieldContext field_context{*mesh, AttrDomain::Edge};
fn::FieldEvaluator selection_evaluator{field_context, mesh->edges_num};
selection_evaluator.set_selection(selection_field);
selection_evaluator.evaluate();
const IndexMask mask = selection_evaluator.get_evaluated_selection_as_mask();
if (mask.is_empty()) {
return;
}
geometry::split_edges(
*geometry_set.get_mesh_for_write(), mask, params.get_attribute_filter("Mesh"_ustr));
}
});
params.set_output("Mesh"_ustr, std::move(geometry_set));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeSplitEdges"_ustr, GEO_NODE_SPLIT_EDGES);
ntype.ui_name = "Split Edges";
ntype.ui_description = "Duplicate mesh edges and break connections with the surrounding faces";
ntype.enum_name_legacy = "SPLIT_EDGES";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_edge_split_cc

View File

@@ -0,0 +1,115 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_mesh_types.h"
#include "BKE_mesh_mapping.hh"
#include "BLI_atomic_disjoint_set.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_edges_to_face_groups_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Bool>("Boundary Edges"_ustr)
.default_value(true)
.hide_value()
.structure_type(StructureType::Field)
.description("Edges used to split faces into separate groups");
b.add_output<decl::Int>("Face Group ID"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("Index of the face group inside each boundary edge region");
}
/** Join all unique unordered combinations of indices. */
static void join_indices(AtomicDisjointSet &set, const Span<int> indices)
{
for (const int i : indices.index_range().drop_back(1)) {
set.join(indices[i], indices[i + 1]);
}
}
class FaceSetFromBoundariesInput final : public bke::MeshFieldInput {
private:
Field<bool> non_boundary_edge_field_;
public:
FaceSetFromBoundariesInput(Field<bool> selection)
: bke::MeshFieldInput(CPPType::get<int>(), "Edges to Face Groups"),
non_boundary_edge_field_(std::move(selection))
{
}
GVArray get_varray_for_context(const Mesh &mesh,
const AttrDomain domain,
const IndexMask & /*mask*/) const final
{
const bke::MeshFieldContext context{mesh, AttrDomain::Edge};
fn::FieldEvaluator evaluator{context, mesh.edges_num};
evaluator.add(non_boundary_edge_field_);
evaluator.evaluate();
const IndexMask non_boundary_edges = evaluator.get_evaluated_as_mask(0);
const OffsetIndices faces = mesh.faces();
Array<int> edge_to_face_offsets;
Array<int> edge_to_face_indices;
const GroupedSpan<int> edge_to_face_map = bke::mesh::build_edge_to_face_map(
faces, mesh.corner_edges(), mesh.edges_num, edge_to_face_offsets, edge_to_face_indices);
AtomicDisjointSet islands(faces.size());
non_boundary_edges.foreach_index(
[&](const int edge) { join_indices(islands, edge_to_face_map[edge]); },
exec_mode::grain_size(2048));
Array<int> output(faces.size());
islands.calc_reduced_ids(output);
return mesh.attributes().adapt_domain(
VArray<int>::from_container(std::move(output)), AttrDomain::Face, domain);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(non_boundary_edge_field_));
}
std::optional<AttrDomain> preferred_domain(const Mesh & /*mesh*/) const final
{
return AttrDomain::Face;
}
};
static void geo_node_exec(GeoNodeExecParams params)
{
Field<bool> boundary_edges = params.extract_input<Field<bool>>("Boundary Edges"_ustr);
Field<bool> non_boundary_edges = fn::invert_boolean_field(std::move(boundary_edges));
params.set_output(
"Face Group ID"_ustr,
Field<int>::from_input<FaceSetFromBoundariesInput>(std::move(non_boundary_edges)));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeEdgesToFaceGroups"_ustr, GEO_NODE_EDGES_TO_FACE_GROUPS);
ntype.ui_name = "Edges to Face Groups";
ntype.ui_description = "Group faces into regions surrounded by the selected boundary edges";
ntype.enum_name_legacy = "EDGES_TO_FACE_GROUPS";
ntype.nclass = NODE_CLASS_INPUT;
ntype.geometry_node_execute = geo_node_exec;
ntype.declare = node_declare;
ntype.default_width = bke::NodeWidth::_160;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_edges_to_face_groups_cc

View File

@@ -0,0 +1,211 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "BKE_node_tree_reference_lifetimes.hh"
#include "NOD_node_extra_info.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "RNA_enum_types.hh"
#include "COM_node_operation.hh"
#include "COM_result.hh"
namespace blender {
namespace nodes::node_geo_enable_output_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
b.add_input<decl::Bool>("Enable"_ustr)
.default_value(false)
.structure_type(StructureType::Single);
const bNode *node = b.node_or_null();
if (!node) {
return;
}
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
b.add_input(data_type, "Value"_ustr).hide_value().structure_type(StructureType::Dynamic);
b.add_output(data_type, "Value"_ustr)
.align_with_previous()
.inferred_structure_type()
.propagate_all();
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
}
class LazyFunctionForEnableOutputNode : public LazyFunction {
const bNode &node_;
public:
LazyFunctionForEnableOutputNode(const bNode &node, MutableSpan<int> r_lf_index_by_bsocket)
: node_(node)
{
r_lf_index_by_bsocket[node.input_socket(0).index_in_tree()] = inputs_.append_and_get_index_as(
"Enable", CPPType::get<SocketValueVariant>());
r_lf_index_by_bsocket[node.input_socket(1).index_in_tree()] = inputs_.append_and_get_index_as(
"Value", CPPType::get<SocketValueVariant>(), lf::ValueUsage::Maybe);
r_lf_index_by_bsocket[node.output_socket(0).index_in_tree()] =
outputs_.append_and_get_index_as("Value", CPPType::get<SocketValueVariant>());
}
void execute_impl(lf::Params &params, const lf::Context & /*context*/) const override
{
const bke::SocketValueVariant enable_variant = params.get_input<bke::SocketValueVariant>(0);
if (!enable_variant.is_single()) {
set_default_remaining_node_outputs(params, node_);
return;
}
const bool keep = enable_variant.get<bool>();
if (!keep) {
set_default_remaining_node_outputs(params, node_);
return;
}
const bke::SocketValueVariant *value_variant =
params.try_get_input_data_ptr_or_request<bke::SocketValueVariant>(1);
if (!value_variant) {
/* Wait until the value is available. */
return;
}
params.set_output(0, std::move(*value_variant));
}
};
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = SOCK_FLOAT;
}
using namespace blender::compositor;
class EnableOutputOperation : public NodeOperation {
public:
using NodeOperation::NodeOperation;
void execute() override
{
const bool keep = this->get_input("Enable").get_single_value_default<bool>();
Result &output = this->get_result("Value");
if (keep) {
const Result &input = this->get_input("Value");
output.share_data(input);
}
else {
output.allocate_invalid();
}
}
};
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
{
return new EnableOutputOperation(context, node);
}
static void node_extra_info(NodeExtraInfoParams &params)
{
params.tree.ensure_topology_cache();
const bNodeSocket &output_socket = params.node.output_socket(0);
if (!output_socket.is_directly_linked()) {
return;
}
for (const bNodeSocket *target_socket : output_socket.logically_linked_sockets()) {
const bNode &target_node = target_socket->owner_node();
if (!target_node.is_group_output() && !target_node.is_reroute()) {
NodeExtraInfoRow row;
row.text = RPT_("Invalid Output Link");
row.tooltip = TIP_("This node should be linked to the group output node");
row.icon = ICON_ERROR;
params.rows.append(std::move(row));
return;
}
}
}
static const EnumPropertyItem *data_type_items_callback(bContext * /*C*/,
PointerRNA *ptr,
PropertyRNA * /*prop*/,
bool *r_free)
{
*r_free = true;
const bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bke::bNodeTreeType *ntree_type = ntree.typeinfo;
return enum_items_filter(
rna_enum_node_socket_data_type_items, [&](const EnumPropertyItem &item) -> bool {
bke::bNodeSocketType *socket_type = bke::node_socket_type_find_static(item.value);
return ntree_type->valid_socket_type(ntree_type, socket_type);
});
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"",
rna_enum_node_socket_data_type_items,
NOD_inline_enum_accessors(custom1),
SOCK_FLOAT,
data_type_items_callback);
}
static const bNodeSocket *node_internally_linked_input(const bNodeTree & /*tree*/,
const bNode &node,
const bNodeSocket &output_socket)
{
/* Internal links should always map corresponding input and output sockets. */
return node.input_by_identifier(output_socket.identifier_ustr());
}
static void node_register()
{
static bke::bNodeType ntype;
geo_cmp_node_type_base(&ntype, "NodeEnableOutput"_ustr);
ntype.ui_name = "Enable Output";
ntype.ui_description = "Either pass through the input value or output the fallback value";
ntype.nclass = NODE_CLASS_INTERFACE;
ntype.ignore_inferred_input_socket_visibility = true;
ntype.initfunc = node_init;
ntype.draw_buttons = node_layout;
ntype.declare = node_declare;
ntype.get_compositor_operation = get_compositor_operation;
ntype.get_extra_info = node_extra_info;
ntype.internally_linked_input = node_internally_linked_input;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace nodes::node_geo_enable_output_cc
namespace nodes {
std::unique_ptr<LazyFunction> get_enable_output_node_lazy_function(
const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
{
using namespace node_geo_enable_output_cc;
return std::make_unique<LazyFunctionForEnableOutputNode>(
node, own_lf_graph_info.mapping.lf_index_by_bsocket);
}
} // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,123 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "RNA_enum_types.hh"
#include "NOD_socket_search_link.hh"
namespace blender::nodes::node_geo_evaluate_at_index_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
const bNode *node = b.node_or_null();
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node->custom2);
b.add_input(data_type, "Value"_ustr).hide_value().structure_type(StructureType::Field);
b.add_output(data_type, "Value"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.align_with_previous();
}
b.add_input<decl::Int>("Index"_ustr).min(0).structure_type(StructureType::Field);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
layout.prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = int(AttrDomain::Point);
node->custom2 = CD_PROP_FLOAT;
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const bke::bNodeType &node_type = params.node_type();
const std::optional<eCustomDataType> type = bke::socket_type_to_custom_data_type(
params.other_socket().type);
if (type && *type != CD_PROP_STRING) {
params.add_item(IFACE_("Value"), [node_type, type](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
node.custom2 = *type;
params.update_and_connect_available_socket(node, "Value"_ustr);
});
if (params.in_out() == SOCK_IN) {
params.add_item(
IFACE_("Index"),
[node_type, type](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
node.custom2 = *type;
params.update_and_connect_available_socket(node, "Index"_ustr);
},
-1);
}
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
const bNode &node = params.node();
const AttrDomain domain = AttrDomain(node.custom1);
GField output_field = GField::from_input<bke::EvaluateAtIndexInput>(
params.extract_input<Field<int>>("Index"_ustr),
params.extract_input<GField>("Value"_ustr),
domain);
params.set_output<GField>("Value"_ustr, std::move(output_field));
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"domain",
"Domain",
"Domain the field is evaluated in",
rna_enum_attribute_domain_items,
NOD_inline_enum_accessors(custom1),
int(AttrDomain::Point));
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"",
rna_enum_attribute_type_items,
NOD_inline_enum_accessors(custom2),
CD_PROP_FLOAT,
enums::attribute_type_type_with_socket_fn);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeFieldAtIndex"_ustr, GEO_NODE_EVALUATE_AT_INDEX);
ntype.ui_name = "Evaluate at Index";
ntype.ui_description = "Retrieve data of other elements in the context's geometry";
ntype.enum_name_legacy = "FIELD_AT_INDEX";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_evaluate_at_index_cc

View File

@@ -0,0 +1,348 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "NOD_geo_closure.hh"
#include "NOD_geometry_nodes_closure_signature.hh"
#include "NOD_socket_items_blend.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "NOD_socket_search_link.hh"
#include "NOD_sync_sockets.hh"
#include "BKE_idprop.hh"
#include "BKE_node_tree_reference_lifetimes.hh"
#include "BLO_read_write.hh"
#include "node_geometry_util.hh"
#include "shader/node_shader_util.hh"
namespace blender {
namespace nodes::node_geo_evaluate_closure_cc {
NODE_STORAGE_FUNCS(NodeEvaluateClosure)
static void create_all_reference_lifetime_relations(NodeDeclarationBuilder &b)
{
using bke::node_tree_reference_lifetimes::can_contain_reference;
using bke::node_tree_reference_lifetimes::can_contain_referenced_data;
rl::RelationsInNode &relations = b.get_reference_lifetime_relations();
const NodeDeclaration &node_decl = b.declaration();
for (const SocketDeclaration *input : node_decl.inputs) {
if (can_contain_reference(input->socket_type)) {
for (const SocketDeclaration *other_input : node_decl.inputs) {
if (can_contain_referenced_data(other_input->socket_type)) {
relations.use_relations.append({input->index, other_input->index});
}
}
for (const SocketDeclaration *output : node_decl.outputs) {
if (can_contain_reference(output->socket_type)) {
relations.reference_propagations.append({input->index, output->index});
}
}
}
if (can_contain_referenced_data(input->socket_type)) {
for (const SocketDeclaration *output : node_decl.outputs) {
if (can_contain_referenced_data(output->socket_type)) {
relations.data_propagations.append({input->index, output->index});
}
}
}
}
for (const SocketDeclaration *output : node_decl.outputs) {
if (can_contain_reference(output->socket_type)) {
for (const SocketDeclaration *other_output : node_decl.outputs) {
if (can_contain_referenced_data(other_output->socket_type)) {
relations.available_relations.append({output->index, other_output->index});
}
}
}
}
}
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Closure>("Closure"_ustr).create_signature([](const bNode &node) {
const auto &storage = node_storage(node);
return nodes::ClosureSignature::from_evaluate_closure_node(
node, storage.flag & NODE_EVALUATE_CLOSURE_FLAG_DEFINE_SIGNATURE);
});
const bNode *node = b.node_or_null();
const bNodeTree *tree = b.tree_or_null();
auto &panel = b.add_panel("Interface"_ustr);
if (node) {
const auto &storage = node_storage(*node);
for (const int i : IndexRange(storage.output_items.items_num)) {
const NodeEvaluateClosureOutputItem &item = storage.output_items.items[i];
const eNodeSocketDatatype socket_type = item.socket_type;
const UString identifier(
EvaluateClosureOutputItemsAccessor::socket_identifier_for_item(item));
auto &decl = panel.add_output(socket_type, UString(item.name), identifier);
decl.socket_name_ptr(
&tree->id, *EvaluateClosureOutputItemsAccessor::item_srna, &item, "name");
if (item.structure_type != NodeSocketInterfaceStructureType::Auto) {
decl.structure_type(StructureType(item.structure_type));
}
else {
decl.structure_type(StructureType::Dynamic);
}
}
panel.add_output<decl::Extend>(""_ustr, "__extend__"_ustr)
.custom_draw(
socket_items::ui::draw_extend_socket_fn<EvaluateClosureOutputItemsAccessor>());
for (const int i : IndexRange(storage.input_items.items_num)) {
const NodeEvaluateClosureInputItem &item = storage.input_items.items[i];
const eNodeSocketDatatype socket_type = item.socket_type;
const UString identifier(
EvaluateClosureInputItemsAccessor::socket_identifier_for_item(item));
auto &decl = panel.add_input(socket_type, UString(item.name), identifier);
decl.socket_name_ptr(
&tree->id, *EvaluateClosureInputItemsAccessor::item_srna, &item, "name");
if (item.structure_type != NodeSocketInterfaceStructureType::Auto) {
decl.structure_type(StructureType(item.structure_type));
}
else {
decl.structure_type(StructureType::Dynamic);
}
}
panel.add_input<decl::Extend>(""_ustr, "__extend__"_ustr)
.custom_draw(socket_items::ui::draw_extend_socket_fn<EvaluateClosureInputItemsAccessor>());
/* This creates all possible reference lifetime relations because the closure could do
* anything. */
create_all_reference_lifetime_relations(b);
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
auto *storage = MEM_new<NodeEvaluateClosure>(__func__);
node->storage = storage;
}
static void node_copy_storage(bNodeTree * /*tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeEvaluateClosure &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_new<NodeEvaluateClosure>(__func__, dna::shallow_copy(src_storage));
dst_node->storage = dst_storage;
socket_items::copy_array<EvaluateClosureInputItemsAccessor>(*src_node, *dst_node);
socket_items::copy_array<EvaluateClosureOutputItemsAccessor>(*src_node, *dst_node);
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<EvaluateClosureInputItemsAccessor>(*node);
socket_items::destruct_array<EvaluateClosureOutputItemsAccessor>(*node);
MEM_delete(static_cast<NodeEvaluateClosure *>(node->storage));
}
static bool node_insert_link(bke::NodeInsertLinkParams &params)
{
if (params.C && params.link.tosock == params.node.inputs.first &&
params.link.fromsock->type == SOCK_CLOSURE)
{
const NodeEvaluateClosure &storage = node_storage(params.node);
if (storage.input_items.items_num == 0 && storage.output_items.items_num == 0) {
SpaceNode *snode = CTX_wm_space_node(params.C);
if (snode && snode->edittree == &params.ntree) {
sync_sockets_evaluate_closure(*snode, params.node, nullptr, params.link.fromsock);
}
}
return true;
}
if (params.link.tonode == &params.node) {
return socket_items::try_add_item_via_any_extend_socket<EvaluateClosureInputItemsAccessor>(
params.ntree, params.node, params.node, params.link);
}
return socket_items::try_add_item_via_any_extend_socket<EvaluateClosureOutputItemsAccessor>(
params.ntree, params.node, params.node, params.link);
}
static void node_layout_ex(ui::Layout &layout, bContext *C, PointerRNA *ptr)
{
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode &node = *static_cast<bNode *>(ptr->data);
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
layout.op("node.sockets_sync", IFACE_("Sync"), ICON_FILE_REFRESH);
layout.prop(ptr, "define_signature", UI_ITEM_NONE, std::nullopt, ICON_NONE);
if (ui::Layout *panel = layout.panel(C, "input_items", false, IFACE_("Input Items"))) {
socket_items::ui::draw_items_list_with_operators<EvaluateClosureInputItemsAccessor>(
C, panel, tree, node);
socket_items::ui::draw_active_item_props<EvaluateClosureInputItemsAccessor>(
tree, node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
panel->prop(item_ptr, "structure_type", UI_ITEM_NONE, IFACE_("Shape"), ICON_NONE);
});
}
if (ui::Layout *panel = layout.panel(C, "output_items", false, IFACE_("Output Items"))) {
socket_items::ui::draw_items_list_with_operators<EvaluateClosureOutputItemsAccessor>(
C, panel, tree, node);
socket_items::ui::draw_active_item_props<EvaluateClosureOutputItemsAccessor>(
tree, node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
panel->prop(item_ptr, "structure_type", UI_ITEM_NONE, IFACE_("Shape"), ICON_NONE);
});
}
}
static const bNodeSocket *node_internally_linked_input(const bNodeTree & /*tree*/,
const bNode & /*node*/,
const bNodeSocket &output_socket)
{
return evaluate_closure_node_internally_linked_input(output_socket);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const bNodeSocket &other_socket = params.other_socket();
if (other_socket.in_out == SOCK_IN) {
params.add_item(IFACE_("Item"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("NodeEvaluateClosure"_ustr);
const auto *item =
socket_items::add_item_with_socket_type_and_name<EvaluateClosureOutputItemsAccessor>(
params.node_tree, node, params.socket.typeinfo->type, params.socket.name);
params.update_and_connect_available_socket(node, UString(item->name));
});
return;
}
if (other_socket.type == SOCK_CLOSURE) {
params.add_item(IFACE_("Closure"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("NodeEvaluateClosure"_ustr);
params.connect_available_socket(node, "Closure"_ustr);
SpaceNode &snode = *CTX_wm_space_node(&params.C);
sync_sockets_evaluate_closure(snode, node, nullptr);
});
}
if (EvaluateClosureInputItemsAccessor::supports_socket_type(other_socket.typeinfo->type,
params.node_tree().type))
{
params.add_item(
IFACE_("Item"),
[](LinkSearchOpParams &params) {
bNode &node = params.add_node("NodeEvaluateClosure"_ustr);
const auto *item =
socket_items::add_item_with_socket_type_and_name<EvaluateClosureInputItemsAccessor>(
params.node_tree, node, params.socket.typeinfo->type, params.socket.name);
nodes::update_node_declaration_and_sockets(params.node_tree, node);
params.connect_available_socket_by_identifier(
node, UString(EvaluateClosureInputItemsAccessor::socket_identifier_for_item(*item)));
},
other_socket.type == SOCK_CLOSURE ? -1 : 0);
}
}
static void node_operators()
{
socket_items::ops::make_common_operators<EvaluateClosureInputItemsAccessor>();
socket_items::ops::make_common_operators<EvaluateClosureOutputItemsAccessor>();
}
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
{
socket_items::blend_write<EvaluateClosureInputItemsAccessor>(&writer, node);
socket_items::blend_write<EvaluateClosureOutputItemsAccessor>(&writer, node);
}
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
{
socket_items::blend_read_data<EvaluateClosureInputItemsAccessor>(&reader, node);
socket_items::blend_read_data<EvaluateClosureOutputItemsAccessor>(&reader, node);
}
static void node_register()
{
static bke::bNodeType ntype;
sh_geo_node_type_base(&ntype, "NodeEvaluateClosure"_ustr, NODE_EVALUATE_CLOSURE);
ntype.ui_name = "Evaluate Closure";
ntype.ui_description = "Execute a given closure";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.insert_link = node_insert_link;
ntype.draw_buttons_ex = node_layout_ex;
ntype.internally_linked_input = node_internally_linked_input;
ntype.gather_link_search_ops = node_gather_link_searches;
ntype.register_operators = node_operators;
ntype.blend_write_storage_content = node_blend_write;
ntype.blend_data_read_storage_content = node_blend_read;
bke::node_type_storage(ntype, "NodeEvaluateClosure", node_free_storage, node_copy_storage);
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace nodes::node_geo_evaluate_closure_cc
namespace nodes {
StructRNA **EvaluateClosureInputItemsAccessor::item_srna = &RNA_NodeEvaluateClosureInputItem;
void EvaluateClosureInputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
writer->write_string(item.name);
}
void EvaluateClosureInputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
StructRNA **EvaluateClosureOutputItemsAccessor::item_srna = &RNA_NodeEvaluateClosureOutputItem;
void EvaluateClosureOutputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
writer->write_string(item.name);
}
void EvaluateClosureOutputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
const bNodeSocket *evaluate_closure_node_internally_linked_input(const bNodeSocket &output_socket)
{
const bNode &node = output_socket.owner_node();
const bNodeTree &tree = node.owner_tree();
BLI_assert(node.is_type("NodeEvaluateClosure"_ustr));
const auto &storage = *static_cast<const NodeEvaluateClosure *>(node.storage);
if (output_socket.index() >= storage.output_items.items_num) {
return nullptr;
}
const NodeEvaluateClosureOutputItem &output_item =
storage.output_items.items[output_socket.index()];
const StringRef output_key = output_item.name;
for (const int i : IndexRange(storage.input_items.items_num)) {
const NodeEvaluateClosureInputItem &input_item = storage.input_items.items[i];
const StringRef input_key = input_item.name;
if (output_key == input_key) {
if (!tree.typeinfo->validate_link ||
tree.typeinfo->validate_link(input_item.socket_type, output_item.socket_type))
{
return &node.input_socket(i + 1);
}
}
}
return nullptr;
}
} // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,114 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "BKE_geometry_fields.hh"
#include "RNA_enum_types.hh"
#include "NOD_socket_search_link.hh"
namespace blender::nodes::node_geo_evaluate_on_domain_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
const bNode *node = b.node_or_null();
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node->custom2);
b.add_input(data_type, "Value"_ustr).structure_type(StructureType::Field);
b.add_output(data_type, "Value"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.align_with_previous();
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
layout.prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = int(AttrDomain::Point);
node->custom2 = CD_PROP_FLOAT;
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const bke::bNodeType &node_type = params.node_type();
const std::optional<eCustomDataType> type = bke::socket_type_to_custom_data_type(
params.other_socket().type);
if (type && *type != CD_PROP_STRING) {
params.add_item(IFACE_("Value"), [node_type, type](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
node.custom2 = *type;
params.update_and_connect_available_socket(node, "Value"_ustr);
});
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
const bNode &node = params.node();
const AttrDomain domain = AttrDomain(node.custom1);
GField src_field = params.extract_input<GField>("Value"_ustr);
params.set_output<GField>(
"Value"_ustr, GField::from_input<bke::EvaluateOnDomainInput>(std::move(src_field), domain));
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"domain",
"Domain",
"Domain the field is evaluated in",
rna_enum_attribute_domain_items,
NOD_inline_enum_accessors(custom1),
int(AttrDomain::Point));
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"",
rna_enum_attribute_type_items,
NOD_inline_enum_accessors(custom2),
CD_PROP_FLOAT,
enums::attribute_type_type_with_socket_fn);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeFieldOnDomain"_ustr, GEO_NODE_EVALUATE_ON_DOMAIN);
ntype.ui_name = "Evaluate on Domain";
ntype.ui_description =
"Retrieve values from a field on a different domain besides the domain from the context";
ntype.enum_name_legacy = "FIELD_ON_DOMAIN";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_evaluate_on_domain_cc

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,336 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute_math.hh"
#include "BLI_array.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_math_vector.h"
#include "BLI_vector.hh"
#include "BLI_virtual_array.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_enum_types.hh"
#include "node_geometry_util.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include <numeric>
namespace blender::nodes::node_geo_field_average_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
const bNode *node = b.node_or_null();
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node->custom1);
b.add_input(data_type, "Value"_ustr)
.structure_type(StructureType::Field)
.description("The values the mean and median will be calculated from");
}
b.add_input<decl::Int>("Group ID"_ustr, "Group Index"_ustr)
.structure_type(StructureType::Field)
.hide_value()
.description("An index used to group values together for multiple separate operations");
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node->custom1);
b.add_output(data_type, "Mean"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("The sum of all values in each group divided by the size of said group");
b.add_output(data_type, "Median"_ustr)
.translation_context(BLT_I18NCONTEXT_ID_NODETREE)
.structure_type(StructureType::Field)
.propagate_references()
.description(
"The middle value in each group when all values are sorted from lowest to highest");
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
layout.prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = CD_PROP_FLOAT;
node->custom2 = int16_t(AttrDomain::Point);
}
enum class Operation { Mean = 0, Median = 1 };
static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT:
case SOCK_BOOLEAN:
case SOCK_INT:
return CD_PROP_FLOAT;
case SOCK_VECTOR:
case SOCK_RGBA:
return CD_PROP_FLOAT3;
default:
return std::nullopt;
}
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const NodeDeclaration &declaration = *params.node_type().static_declaration;
search_link_ops_for_declarations(params, declaration.inputs);
const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
if (!type) {
return;
}
if (params.in_out() == SOCK_OUT) {
params.add_item(
IFACE_("Mean"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldAverage"_ustr);
node.custom1 = *type;
params.update_and_connect_available_socket(node, "Mean"_ustr);
},
0);
params.add_item(
CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, "Median"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldAverage"_ustr);
node.custom1 = *type;
params.update_and_connect_available_socket(node, "Median"_ustr);
},
-1);
}
else {
params.add_item(
IFACE_("Value"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldAverage"_ustr);
node.custom1 = *type;
params.update_and_connect_available_socket(node, "Value"_ustr);
},
0);
}
}
template<typename T> static T calculate_median(MutableSpan<T> values)
{
if constexpr (std::is_same<T, float3>::value) {
Array<float> x_vals(values.size()), y_vals(values.size()), z_vals(values.size());
for (const int i : values.index_range()) {
float3 value = values[i];
x_vals[i] = value.x;
y_vals[i] = value.y;
z_vals[i] = value.z;
}
return float3(calculate_median<float>(x_vals),
calculate_median<float>(y_vals),
calculate_median<float>(z_vals));
}
else {
const auto middle_itr = values.begin() + values.size() / 2;
std::nth_element(values.begin(), middle_itr, values.end());
if (values.size() % 2 == 0) {
auto *const left_middle_itr = std::max_element(values.begin(), middle_itr);
return math::midpoint<T>(*left_middle_itr, *middle_itr);
}
return *middle_itr;
}
}
class FieldAverageInput final : public bke::GeometryFieldInput {
private:
GField input_;
Field<int> group_index_;
AttrDomain source_domain_;
Operation operation_;
public:
FieldAverageInput(const AttrDomain source_domain,
GField input,
Field<int> group_index,
Operation operation)
: bke::GeometryFieldInput(input.cpp_type(), "Calculation"),
input_(std::move(input)),
group_index_(std::move(group_index)),
source_domain_(source_domain),
operation_(operation)
{
}
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
const IndexMask & /*mask*/) const final
{
const AttributeAccessor attributes = *context.attributes();
const int64_t domain_size = attributes.domain_size(source_domain_);
if (domain_size == 0) {
return {};
}
const bke::GeometryFieldContext source_context{context, source_domain_};
fn::FieldEvaluator evaluator{source_context, domain_size};
evaluator.add(input_);
evaluator.add(group_index_);
evaluator.evaluate();
const GVArray g_values = evaluator.get_evaluated(0);
const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
GVArray g_outputs;
g_values.type().to_static_type<int, float, float3>([&]<typename T>() {
const VArraySpan<T> values = g_values.typed<T>();
if (operation_ == Operation::Mean) {
if (group_indices.is_single()) {
const T mean = std::reduce(values.begin(), values.end(), T()) / domain_size;
g_outputs = VArray<T>::from_single(mean, domain_size);
}
else {
Map<int, std::pair<T, int>> sum_and_counts;
for (const int i : values.index_range()) {
auto &pair = sum_and_counts.lookup_or_add(group_indices[i], std::make_pair(T(), 0));
pair.first = pair.first + values[i];
pair.second = pair.second + 1;
}
Array<T> outputs(domain_size);
for (const int i : values.index_range()) {
const auto &pair = sum_and_counts.lookup(group_indices[i]);
outputs[i] = pair.first / pair.second;
}
g_outputs = VArray<T>::from_container(std::move(outputs));
}
}
else {
if (group_indices.is_single()) {
Array<T> sorted_values(values);
T median = calculate_median<T>(sorted_values);
g_outputs = VArray<T>::from_single(median, domain_size);
}
else {
Map<int, Vector<T>> groups;
for (const int i : values.index_range()) {
groups.lookup_or_add(group_indices[i], Vector<T>()).append(values[i]);
}
Map<int, T> medians;
for (MutableMapItem<int, Vector<T>> group : groups.items()) {
medians.add(group.key, calculate_median<T>(group.value));
}
Array<T> outputs(domain_size);
for (const int i : values.index_range()) {
outputs[i] = medians.lookup(group_indices[i]);
}
g_outputs = VArray<T>::from_container(std::move(outputs));
}
}
});
return attributes.adapt_domain(std::move(g_outputs), source_domain_, context.domain());
}
void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const final
{
fn(input_);
fn(group_index_);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(input_));
hash.add(deep_hash_cache.ensure(group_index_));
hash.add(source_domain_);
hash.add(operation_);
}
std::optional<AttrDomain> preferred_domain(
const GeometryComponent & /*component*/) const override
{
return source_domain_;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
const AttrDomain source_domain = AttrDomain(params.node().custom2);
const Field<int> group_index_field = params.extract_input<Field<int>>("Group Index"_ustr);
const GField input_field = params.extract_input<GField>("Value"_ustr);
if (params.output_is_required("Mean"_ustr)) {
params.set_output<GField>("Mean"_ustr,
GField::from_input<FieldAverageInput>(
source_domain, input_field, group_index_field, Operation::Mean));
}
if (params.output_is_required("Median"_ustr)) {
params.set_output<GField>(
"Median"_ustr,
GField::from_input<FieldAverageInput>(
source_domain, input_field, group_index_field, Operation::Median));
}
}
static void node_rna(StructRNA *srna)
{
static EnumPropertyItem items[] = {
{CD_PROP_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, "Float", "Floating-point value"},
{CD_PROP_FLOAT3,
"FLOAT_VECTOR",
ICON_NODE_SOCKET_VECTOR,
"Vector",
"3D vector with floating-point values"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"Type of data the outputs are calculated from",
items,
NOD_inline_enum_accessors(custom1),
CD_PROP_FLOAT);
RNA_def_node_enum(srna,
"domain",
"Domain",
"",
rna_enum_attribute_domain_items,
NOD_inline_enum_accessors(custom2),
int(AttrDomain::Point),
nullptr,
true);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeFieldAverage"_ustr);
ntype.ui_name = "Field Average";
ntype.ui_description = "Calculate the mean and median of a given field";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.geometry_node_execute = node_geo_exec;
ntype.initfunc = node_init;
ntype.draw_buttons = node_layout;
ntype.declare = node_declare;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_field_average_cc

View File

@@ -0,0 +1,322 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute_math.hh"
#include "BLI_array.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_virtual_array.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_enum_types.hh"
#include "node_geometry_util.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_geo_field_min_and_max_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
const bNode *node = b.node_or_null();
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node->custom1);
b.add_input(data_type, "Value"_ustr)
.structure_type(StructureType::Field)
.description("The values the minimum and maximum will be calculated from");
}
b.add_input<decl::Int>("Group ID"_ustr, "Group Index"_ustr)
.structure_type(StructureType::Field)
.hide_value()
.description("An index used to group values together for multiple separate operations");
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node->custom1);
b.add_output(data_type, "Min"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("The lowest value in each group");
b.add_output(data_type, "Max"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("The highest value in each group");
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
layout.prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = CD_PROP_FLOAT;
node->custom2 = int16_t(AttrDomain::Point);
}
enum class Operation { Min = 0, Max = 1 };
static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT:
return CD_PROP_FLOAT;
case SOCK_BOOLEAN:
case SOCK_INT:
return CD_PROP_INT32;
case SOCK_VECTOR:
case SOCK_RGBA:
case SOCK_ROTATION:
return CD_PROP_FLOAT3;
default:
return std::nullopt;
}
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const NodeDeclaration &declaration = *params.node_type().static_declaration;
search_link_ops_for_declarations(params, declaration.inputs);
const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
if (!type) {
return;
}
if (params.in_out() == SOCK_OUT) {
params.add_item(
IFACE_("Min"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldMinAndMax"_ustr);
node.custom1 = *type;
params.update_and_connect_available_socket(node, "Min"_ustr);
},
0);
params.add_item(
IFACE_("Max"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldMinAndMax"_ustr);
node.custom1 = *type;
params.update_and_connect_available_socket(node, "Max"_ustr);
},
-1);
}
else {
params.add_item(
IFACE_("Value"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldMinAndMax"_ustr);
node.custom1 = *type;
params.update_and_connect_available_socket(node, "Value"_ustr);
},
0);
}
}
template<typename T> struct MinMaxInfo {
static inline const T min_initial_value = []() {
if constexpr (std::is_same_v<T, float3>) {
return float3(std::numeric_limits<float>::max());
}
else {
return std::numeric_limits<T>::max();
}
}();
static inline const T max_initial_value = []() {
if constexpr (std::is_same_v<T, float3>) {
return float3(std::numeric_limits<float>::lowest());
}
else {
return std::numeric_limits<T>::lowest();
}
}();
};
class FieldMinMaxInput final : public bke::GeometryFieldInput {
private:
GField input_;
Field<int> group_index_;
AttrDomain source_domain_;
Operation operation_;
public:
FieldMinMaxInput(const AttrDomain source_domain,
GField input,
Field<int> group_index,
Operation operation)
: bke::GeometryFieldInput(input.cpp_type(), "Calculation"),
input_(std::move(input)),
group_index_(std::move(group_index)),
source_domain_(source_domain),
operation_(operation)
{
}
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
const IndexMask & /*mask*/) const final
{
const AttributeAccessor attributes = *context.attributes();
const int64_t domain_size = attributes.domain_size(source_domain_);
if (domain_size == 0) {
return {};
}
const bke::GeometryFieldContext source_context{context, source_domain_};
fn::FieldEvaluator evaluator{source_context, domain_size};
evaluator.add(input_);
evaluator.add(group_index_);
evaluator.evaluate();
const GVArray g_values = evaluator.get_evaluated(0);
const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
GVArray g_outputs;
g_values.type().to_static_type<int, float, float3>([&]<typename T>() {
const VArray<T> values = g_values.typed<T>();
if (operation_ == Operation::Min) {
if (group_indices.is_single()) {
T result = MinMaxInfo<T>::min_initial_value;
for (const int i : values.index_range()) {
result = math::min(result, values[i]);
}
g_outputs = VArray<T>::from_single(result, domain_size);
}
else {
Map<int, T> results;
for (const int i : values.index_range()) {
T &value = results.lookup_or_add(group_indices[i], MinMaxInfo<T>::min_initial_value);
value = math::min(value, values[i]);
}
Array<T> outputs(domain_size);
for (const int i : values.index_range()) {
outputs[i] = results.lookup(group_indices[i]);
}
g_outputs = VArray<T>::from_container(std::move(outputs));
}
}
else {
if (group_indices.is_single()) {
T result = MinMaxInfo<T>::max_initial_value;
for (const int i : values.index_range()) {
result = math::max(result, values[i]);
}
g_outputs = VArray<T>::from_single(result, domain_size);
}
else {
Map<int, T> results;
for (const int i : values.index_range()) {
T &value = results.lookup_or_add(group_indices[i], MinMaxInfo<T>::max_initial_value);
value = math::max(value, values[i]);
}
Array<T> outputs(domain_size);
for (const int i : values.index_range()) {
outputs[i] = results.lookup(group_indices[i]);
}
g_outputs = VArray<T>::from_container(std::move(outputs));
}
}
});
return attributes.adapt_domain(std::move(g_outputs), source_domain_, context.domain());
}
void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const final
{
fn(input_);
fn(group_index_);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(input_));
hash.add(deep_hash_cache.ensure(group_index_));
hash.add(source_domain_);
hash.add(operation_);
}
std::optional<AttrDomain> preferred_domain(
const GeometryComponent & /*component*/) const override
{
return source_domain_;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
const AttrDomain source_domain = AttrDomain(params.node().custom2);
const Field<int> group_index_field = params.extract_input<Field<int>>("Group Index"_ustr);
const GField input_field = params.extract_input<GField>("Value"_ustr);
if (params.output_is_required("Min"_ustr)) {
params.set_output<GField>("Min"_ustr,
GField::from_input<FieldMinMaxInput>(
source_domain, input_field, group_index_field, Operation::Min));
}
if (params.output_is_required("Max"_ustr)) {
params.set_output<GField>("Max"_ustr,
GField::from_input<FieldMinMaxInput>(
source_domain, input_field, group_index_field, Operation::Max));
}
}
static void node_rna(StructRNA *srna)
{
static EnumPropertyItem items[] = {
{CD_PROP_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, "Float", "Floating-point value"},
{CD_PROP_INT32, "INT", ICON_NODE_SOCKET_INT, "Integer", "32-bit integer"},
{CD_PROP_FLOAT3,
"FLOAT_VECTOR",
ICON_NODE_SOCKET_VECTOR,
"Vector",
"3D vector with floating-point values"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"Type of data the outputs are calculated from",
items,
NOD_inline_enum_accessors(custom1),
CD_PROP_FLOAT);
RNA_def_node_enum(srna,
"domain",
"Domain",
"",
rna_enum_attribute_domain_items,
NOD_inline_enum_accessors(custom2),
int(AttrDomain::Point),
nullptr,
true);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeFieldMinAndMax"_ustr);
ntype.ui_name = "Field Min & Max";
ntype.ui_description = "Calculate the minimum and maximum of a given field";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.geometry_node_execute = node_geo_exec;
ntype.initfunc = node_init;
ntype.draw_buttons = node_layout;
ntype.declare = node_declare;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_field_min_and_max_cc

View File

@@ -0,0 +1,447 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
#include "NOD_geo_field_to_grid.hh"
#include "NOD_socket_items_blend.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.hh"
#include "BLO_read_write.hh"
#ifdef WITH_OPENVDB
# include "BKE_volume_grid_fields.hh"
# include "BKE_volume_grid_process.hh"
#endif
namespace blender {
namespace nodes::node_geo_field_to_grid_cc {
NODE_STORAGE_FUNCS(GeometryNodeFieldToGrid)
using ItemsAccessor = FieldToGridItemsAccessor;
namespace grid = bke::volume_grid;
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
const bNodeTree *tree = b.tree_or_null();
const bNode *node = b.node_or_null();
if (!node || !tree) {
return;
}
const GeometryNodeFieldToGrid &storage = node_storage(*node);
const eNodeSocketDatatype data_type = storage.data_type;
b.add_input(data_type, "Topology"_ustr).structure_type(StructureType::Grid);
const Span<GeometryNodeFieldToGridItem> items(storage.items, storage.items_num);
for (const int i : items.index_range()) {
const GeometryNodeFieldToGridItem &item = items[i];
const eNodeSocketDatatype data_type = item.data_type;
const UString name(item.name);
const UString input_identifier(ItemsAccessor::input_socket_identifier_for_item(item));
const UString output_identifier(ItemsAccessor::output_socket_identifier_for_item(item));
b.add_input(data_type, name, input_identifier)
.structure_type(StructureType::Field)
.socket_name_ptr(&tree->id, *FieldToGridItemsAccessor::item_srna, &item, "name");
b.add_output(data_type, name, output_identifier)
.structure_type(StructureType::Grid)
.align_with_previous()
.description("Output grid with evaluated field values");
}
b.add_input<decl::Extend>(""_ustr, "__extend__"_ustr)
.structure_type(StructureType::Field)
.custom_draw(socket_items::ui::draw_extend_socket_fn<FieldToGridItemsAccessor>());
b.add_output<decl::Extend>(""_ustr, "__extend__"_ustr)
.structure_type(StructureType::Grid)
.align_with_previous();
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_layout_ex(ui::Layout &layout, bContext *C, PointerRNA *ptr)
{
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode &node = *static_cast<bNode *>(ptr->data);
if (ui::Layout *panel = layout.panel(C, "field_to_grid_items", false, IFACE_("Fields"))) {
socket_items::ui::draw_items_list_with_operators<ItemsAccessor>(C, panel, tree, node);
socket_items::ui::draw_active_item_props<ItemsAccessor>(tree, node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "data_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
});
}
}
static std::optional<eNodeSocketDatatype> node_type_for_socket_type(const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT:
return SOCK_FLOAT;
case SOCK_BOOLEAN:
return SOCK_BOOLEAN;
case SOCK_INT:
return SOCK_INT;
case SOCK_VECTOR:
case SOCK_RGBA:
return SOCK_VECTOR;
default:
return std::nullopt;
}
}
static void node_gather_link_search_ops(GatherLinkSearchOpParams &params)
{
const std::optional<eNodeSocketDatatype> data_type = node_type_for_socket_type(
params.other_socket());
if (!data_type) {
return;
}
if (params.in_out() == SOCK_IN) {
params.add_item(IFACE_("Topology"), [data_type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldToGrid"_ustr);
node_storage(node).data_type = *data_type;
params.update_and_connect_available_socket(node, "Topology"_ustr);
});
params.add_item(IFACE_("Field"), [data_type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldToGrid"_ustr);
const auto *item = socket_items::add_item_with_socket_type_and_name<ItemsAccessor>(
params.node_tree, node, *data_type, params.socket.name);
params.update_and_connect_available_socket_by_identifier(
node, UString(FieldToGridItemsAccessor::input_socket_identifier_for_item(*item)));
});
}
else {
params.add_item(IFACE_("Grid"), [data_type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldToGrid"_ustr);
socket_items::add_item_with_socket_type_and_name<ItemsAccessor>(
params.node_tree, node, *data_type, params.socket.name);
params.update_and_connect_available_socket(node, UString(params.socket.name));
});
}
}
#ifdef WITH_OPENVDB
BLI_NOINLINE static void process_leaf_node(const Span<fn::GField> fields,
const openvdb::math::Transform &transform,
const grid::LeafNodeMask &leaf_node_mask,
const openvdb::CoordBBox &leaf_bbox,
const grid::GetVoxelsFn get_voxels_fn,
const Span<openvdb::GridBase::Ptr> output_grids)
{
AlignedBuffer<8192, 8> allocation_buffer;
ResourceScope scope(allocation_buffer);
const IndexMask index_mask = IndexMask::from_predicate(
IndexRange(grid::LeafNodeMask::SIZE),
scope.allocator(),
[&](const int64_t i) { return leaf_node_mask.isOn(i); },
exec_mode::serial);
const openvdb::Coord any_voxel_in_leaf = leaf_bbox.min();
MutableSpan<openvdb::Coord> voxels = scope.allocator().allocate_array<openvdb::Coord>(
index_mask.min_array_size());
get_voxels_fn(voxels);
bke::VoxelFieldContext field_context{transform, voxels};
fn::FieldEvaluator evaluator{field_context, &index_mask};
Array<MutableSpan<bool>> boolean_outputs(fields.size());
for (const int i : fields.index_range()) {
const CPPType &type = fields[i].cpp_type();
grid::to_typed_grid(*output_grids[i], [&](auto &grid) {
using GridT = typename std::decay_t<decltype(grid)>;
using ValueT = typename GridT::ValueType;
auto &tree = grid.tree();
auto *leaf_node = tree.probeLeaf(any_voxel_in_leaf);
/* Should have been added before. */
BLI_assert(leaf_node);
/* Boolean grids are special because they encode the values as bitmask. */
if constexpr (std::is_same_v<ValueT, bool>) {
boolean_outputs[i] = scope.allocator().allocate_array<bool>(index_mask.min_array_size());
evaluator.add_with_destination(fields[i], boolean_outputs[i]);
}
else {
/* Write directly into the buffer of the output leaf node. */
ValueT *buffer = leaf_node->buffer().data();
evaluator.add_with_destination(fields[i],
GMutableSpan(type, buffer, grid::LeafNodeMask::SIZE));
}
});
}
evaluator.evaluate();
for (const int i : fields.index_range()) {
if (!boolean_outputs[i].is_empty()) {
grid::set_mask_leaf_buffer_from_bools(static_cast<openvdb::BoolGrid &>(*output_grids[i]),
boolean_outputs[i],
index_mask,
voxels);
}
}
}
BLI_NOINLINE static void process_voxels(const Span<fn::GField> fields,
const openvdb::math::Transform &transform,
const Span<openvdb::Coord> voxels,
const Span<openvdb::GridBase::Ptr> output_grids)
{
const int64_t voxels_num = voxels.size();
AlignedBuffer<8192, 8> allocation_buffer;
ResourceScope scope(allocation_buffer);
bke::VoxelFieldContext field_context{transform, voxels};
fn::FieldEvaluator evaluator{field_context, voxels_num};
Array<GMutableSpan> output_values(output_grids.size());
for (const int i : fields.index_range()) {
const CPPType &type = fields[i].cpp_type();
output_values[i] = {type, scope.allocator().allocate_array(type, voxels_num), voxels_num};
evaluator.add_with_destination(fields[i], output_values[i]);
}
evaluator.evaluate();
for (const int i : fields.index_range()) {
grid::set_grid_values(*output_grids[i], output_values[i], voxels);
}
}
BLI_NOINLINE static void process_tiles(const Span<fn::GField> fields,
const openvdb::math::Transform &transform,
const Span<openvdb::CoordBBox> tiles,
const Span<openvdb::GridBase::Ptr> output_grids)
{
const int64_t tiles_num = tiles.size();
AlignedBuffer<8192, 8> allocation_buffer;
ResourceScope scope(allocation_buffer);
bke::TilesFieldContext field_context{transform, tiles};
fn::FieldEvaluator evaluator{field_context, tiles_num};
Array<GMutableSpan> output_values(output_grids.size());
for (const int i : fields.index_range()) {
const CPPType &type = fields[i].cpp_type();
output_values[i] = {type, scope.allocator().allocate_array(type, tiles_num), tiles_num};
evaluator.add_with_destination(fields[i], output_values[i]);
}
evaluator.evaluate();
for (const int i : fields.index_range()) {
grid::set_tile_values(*output_grids[i], output_values[i], tiles);
}
}
BLI_NOINLINE static void process_background(const Span<fn::GField> fields,
const openvdb::math::Transform &transform,
const Span<openvdb::GridBase::Ptr> output_grids)
{
AlignedBuffer<256, 8> allocation_buffer;
ResourceScope scope(allocation_buffer);
static const openvdb::CoordBBox background_space = openvdb::CoordBBox::inf();
bke::TilesFieldContext field_context(transform, Span<openvdb::CoordBBox>(&background_space, 1));
fn::FieldEvaluator evaluator(field_context, 1);
Array<GMutablePointer> output_values(output_grids.size());
for (const int i : fields.index_range()) {
const CPPType &type = fields[i].cpp_type();
output_values[i] = {type, scope.allocator().allocate(type)};
evaluator.add_with_destination(fields[i], GMutableSpan{type, output_values[i].get(), 1});
}
evaluator.evaluate();
for (const int i : fields.index_range()) {
grid::set_grid_background(*output_grids[i], output_values[i]);
}
}
#endif
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
const GeometryNodeFieldToGrid &storage = node_storage(params.node());
const Span<GeometryNodeFieldToGridItem> items(storage.items, storage.items_num);
bke::GVolumeGrid topology_grid = params.extract_input<bke::GVolumeGrid>("Topology"_ustr);
if (!topology_grid) {
params.error_message_add(NodeWarningType::Error, "The topology grid input is required");
params.set_default_remaining_outputs();
return;
}
bke::VolumeTreeAccessToken tree_token;
const openvdb::GridBase &topology_base = topology_grid->grid(tree_token);
const openvdb::math::Transform &transform = topology_base.transform();
Vector<int> required_items;
for (const int i : items.index_range()) {
if (params.output_is_required(
UString(ItemsAccessor::output_socket_identifier_for_item(items[i]))))
{
required_items.append(i);
}
}
Vector<fn::GField> fields;
fields.reserve(required_items.size());
for (const int i : required_items.index_range()) {
const int item_i = required_items[i];
const std::string identifier = ItemsAccessor::input_socket_identifier_for_item(items[item_i]);
fields.append(params.extract_input<fn::GField>(UString(identifier)));
}
openvdb::MaskTree mask_tree;
grid::to_typed_grid(topology_base,
[&](const auto &grid) { mask_tree.topologyUnion(grid.tree()); });
Vector<openvdb::GridBase::Ptr> output_grids(required_items.size());
for (const int i : required_items.index_range()) {
const int item_i = required_items[i];
const eNodeSocketDatatype socket_type = items[item_i].data_type;
const VolumeGridType grid_type = *bke::socket_type_to_grid_type(socket_type);
output_grids[i] = grid::create_grid_with_topology(mask_tree, transform, grid_type);
}
grid::parallel_grid_topology_tasks(
mask_tree,
[&](const grid::LeafNodeMask &leaf_node_mask,
const openvdb::CoordBBox &leaf_bbox,
const grid::GetVoxelsFn get_voxels_fn) {
process_leaf_node(
fields, transform, leaf_node_mask, leaf_bbox, get_voxels_fn, output_grids);
},
[&](const Span<openvdb::Coord> voxels) {
process_voxels(fields, transform, voxels, output_grids);
},
[&](const Span<openvdb::CoordBBox> tiles) {
process_tiles(fields, transform, tiles, output_grids);
});
process_background(fields, transform, output_grids);
for (const int i : required_items.index_range()) {
const int item_i = required_items[i];
const std::string identifier = ItemsAccessor::output_socket_identifier_for_item(items[item_i]);
params.set_output(UString(identifier), bke::GVolumeGrid(std::move(output_grids[i])));
}
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
GeometryNodeFieldToGrid *data = MEM_new<GeometryNodeFieldToGrid>(__func__);
data->data_type = SOCK_FLOAT;
node->storage = data;
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<ItemsAccessor>(*node);
MEM_delete(static_cast<GeometryNodeFieldToGrid *>(node->storage));
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const GeometryNodeFieldToGrid &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_new<GeometryNodeFieldToGrid>(__func__, dna::shallow_copy(src_storage));
dst_node->storage = dst_storage;
socket_items::copy_array<ItemsAccessor>(*src_node, *dst_node);
}
static void node_operators()
{
socket_items::ops::make_common_operators<ItemsAccessor>();
}
static bool node_insert_link(bke::NodeInsertLinkParams &params)
{
return socket_items::try_add_item_via_any_extend_socket<ItemsAccessor>(
params.ntree, params.node, params.node, params.link);
}
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
{
socket_items::blend_write<ItemsAccessor>(&writer, node);
}
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
{
socket_items::blend_read_data<ItemsAccessor>(&reader, node);
}
static const bNodeSocket *node_internally_linked_input(const bNodeTree & /*tree*/,
const bNode &node,
const bNodeSocket &output_socket)
{
return node.input_by_identifier(output_socket.identifier_ustr());
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeFieldToGrid"_ustr);
ntype.ui_name = "Field to Grid";
ntype.ui_description =
"Create new grids by evaluating new values on an existing volume grid topology";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.initfunc = node_init;
bke::node_type_storage(ntype, "GeometryNodeFieldToGrid", node_free_storage, node_copy_storage);
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
ntype.draw_buttons_ex = node_layout_ex;
ntype.register_operators = node_operators;
ntype.insert_link = node_insert_link;
ntype.ignore_inferred_input_socket_visibility = true;
ntype.gather_link_search_ops = node_gather_link_search_ops;
ntype.internally_linked_input = node_internally_linked_input;
ntype.blend_write_storage_content = node_blend_write;
ntype.blend_data_read_storage_content = node_blend_read;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace nodes::node_geo_field_to_grid_cc
namespace nodes {
StructRNA **FieldToGridItemsAccessor::item_srna = &RNA_GeometryNodeFieldToGridItem;
void FieldToGridItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
writer->write_string(item.name);
}
void FieldToGridItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
} // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,262 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLO_read_write.hh"
#include "NOD_geo_field_to_list.hh"
#include "NOD_geometry_nodes_list.hh"
#include "NOD_socket_items_blend.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_prototypes.hh"
#include "list_function_eval.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_field_to_list_cc {
NODE_STORAGE_FUNCS(GeometryNodeFieldToList)
using ItemsAccessor = FieldToListItemsAccessor;
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Int>("Count"_ustr)
.default_value(1)
.min(1)
.description("The number of elements in the list");
const bNode *node = b.node_or_null();
const bNodeTree *tree = b.tree_or_null();
if (!node) {
return;
}
const GeometryNodeFieldToList &storage = node_storage(*node);
const Span<GeometryNodeFieldToListItem> items(storage.items, storage.items_num);
for (const int i : items.index_range()) {
const GeometryNodeFieldToListItem &item = items[i];
const eNodeSocketDatatype type = item.socket_type;
const std::string input_identifier = ItemsAccessor::input_socket_identifier_for_item(item);
const std::string output_identifier = ItemsAccessor::output_socket_identifier_for_item(item);
const UString name(item.name);
b.add_input(type, name, UString(input_identifier))
.structure_type(StructureType::Field)
.socket_name_ptr(&tree->id, *ItemsAccessor::item_srna, &item, "name");
b.add_output(type, name, UString(output_identifier))
.structure_type(StructureType::List)
.align_with_previous()
.description("Output list with evaluated field values");
}
b.add_input<decl::Extend>(""_ustr, "__extend__"_ustr)
.structure_type(StructureType::Field)
.custom_draw(socket_items::ui::draw_extend_socket_fn<FieldToListItemsAccessor>());
b.add_output<decl::Extend>(""_ustr, "__extend__"_ustr)
.structure_type(StructureType::List)
.align_with_previous();
}
static void node_layout_ex(ui::Layout &layout, bContext *C, PointerRNA *ptr)
{
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode &node = *static_cast<bNode *>(ptr->data);
if (ui::Layout *panel = layout.panel(C, "field_to_list_items", false, IFACE_("Items"))) {
socket_items::ui::draw_items_list_with_operators<ItemsAccessor>(C, panel, tree, node);
socket_items::ui::draw_active_item_props<ItemsAccessor>(tree, node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
});
}
}
static void node_gather_link_search_ops(GatherLinkSearchOpParams &params)
{
const eNodeSocketDatatype data_type = params.other_socket().type;
if (params.in_out() == SOCK_IN) {
if (params.node_tree().typeinfo->validate_link(data_type, SOCK_INT)) {
params.add_item(IFACE_("Count"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldToList"_ustr);
params.update_and_connect_available_socket(node, "Count"_ustr);
});
}
if (ItemsAccessor::supports_socket_type(data_type, NTREE_GEOMETRY)) {
params.add_item(IFACE_("Field"), [data_type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldToList"_ustr);
const auto *item = socket_items::add_item_with_socket_type_and_name<ItemsAccessor>(
params.node_tree, node, data_type, params.socket.name);
params.update_and_connect_available_socket_by_identifier(
node, UString(FieldToListItemsAccessor::input_socket_identifier_for_item(*item)));
});
}
}
else {
if (ItemsAccessor::supports_socket_type(data_type, NTREE_GEOMETRY)) {
params.add_item(IFACE_("List"), [data_type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldToList"_ustr);
socket_items::add_item_with_socket_type_and_name<ItemsAccessor>(
params.node_tree, node, data_type, params.socket.name);
params.update_and_connect_available_socket(node, UString(params.socket.name));
});
}
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
const int count = params.extract_input<int>("Count"_ustr);
if (count < 0) {
params.error_message_add(NodeWarningType::Error, "Count must not be negative");
params.set_default_remaining_outputs();
return;
}
const GeometryNodeFieldToList &storage = node_storage(params.node());
const Span<GeometryNodeFieldToListItem> items(storage.items, storage.items_num);
Vector<int> required_items;
for (const int i : items.index_range()) {
if (params.output_is_required(
UString(ItemsAccessor::output_socket_identifier_for_item(items[i]))))
{
required_items.append(i);
}
}
Vector<fn::GField> fields;
fields.reserve(required_items.size());
for (const int i : required_items.index_range()) {
const int item_i = required_items[i];
const std::string identifier = ItemsAccessor::input_socket_identifier_for_item(items[item_i]);
fields.append(params.extract_input<fn::GField>(UString(identifier)));
}
Vector<GListPtr> lists(required_items.size());
for (const int i : required_items.index_range()) {
const int item_i = required_items[i];
const eNodeSocketDatatype type = items[item_i].socket_type;
const CPPType &cpp_type = *bke::socket_type_to_geo_nodes_base_cpp_type(type);
lists[i] = GList::create(cpp_type, GList::ArrayData::ForUninitialized(cpp_type, count), count);
}
Array<GMutableSpan> list_values(lists.size());
for (const int i : lists.index_range()) {
list_values[i] = {lists[i]->cpp_type(),
const_cast<void *>(std::get<GList::ArrayData>(lists[i]->data()).data),
count};
}
ListFieldContext context;
fn::FieldEvaluator evaluator{context, count};
for (const int i : fields.index_range()) {
GMutableSpan values(lists[i]->cpp_type(),
const_cast<void *>(std::get<GList::ArrayData>(lists[i]->data()).data),
count);
evaluator.add_with_destination(std::move(fields[i]), values);
}
evaluator.evaluate();
for (const int i : required_items.index_range()) {
const int item_i = required_items[i];
const std::string identifier = ItemsAccessor::output_socket_identifier_for_item(items[item_i]);
params.set_output(UString(identifier), std::move(lists[i]));
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->storage = MEM_new<GeometryNodeFieldToList>(__func__);
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<ItemsAccessor>(*node);
MEM_delete_void(node->storage);
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const GeometryNodeFieldToList &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_new<GeometryNodeFieldToList>(__func__, src_storage);
dst_node->storage = dst_storage;
socket_items::copy_array<ItemsAccessor>(*src_node, *dst_node);
}
static void node_operators()
{
socket_items::ops::make_common_operators<ItemsAccessor>();
}
static bool node_insert_link(bke::NodeInsertLinkParams &params)
{
return socket_items::try_add_item_via_any_extend_socket<ItemsAccessor>(
params.ntree, params.node, params.node, params.link);
}
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
{
socket_items::blend_write<ItemsAccessor>(&writer, node);
}
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
{
socket_items::blend_read_data<ItemsAccessor>(&reader, node);
}
static const bNodeSocket *node_internally_linked_input(const bNodeTree & /*tree*/,
const bNode &node,
const bNodeSocket &output_socket)
{
return node.input_by_identifier(output_socket.identifier_ustr());
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeFieldToList"_ustr);
ntype.ui_name = "Field to List";
ntype.ui_description = "Create a list of values";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
blender::bke::node_type_storage(
ntype, "GeometryNodeFieldToList", node_free_storage, node_copy_storage);
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons_ex = node_layout_ex;
ntype.register_operators = node_operators;
ntype.insert_link = node_insert_link;
ntype.ignore_inferred_input_socket_visibility = true;
ntype.gather_link_search_ops = node_gather_link_search_ops;
ntype.internally_linked_input = node_internally_linked_input;
ntype.blend_write_storage_content = node_blend_write;
ntype.blend_data_read_storage_content = node_blend_read;
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_field_to_list_cc
namespace blender::nodes {
StructRNA **FieldToListItemsAccessor::item_srna = &RNA_GeometryNodeFieldToListItem;
void FieldToListItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
writer->write_string(item.name);
}
void FieldToListItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
} // namespace blender::nodes

View File

@@ -0,0 +1,338 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute_math.hh"
#include "BLI_array.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_virtual_array.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_enum_types.hh"
#include "node_geometry_util.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include <numeric>
namespace blender::nodes::node_geo_field_variance_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
const bNode *node = b.node_or_null();
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node->custom1);
b.add_input(data_type, "Value"_ustr)
.structure_type(StructureType::Field)
.description("The values the standard deviation and variance will be calculated from");
}
b.add_input<decl::Int>("Group ID"_ustr, "Group Index"_ustr)
.structure_type(StructureType::Field)
.hide_value()
.description("An index used to group values together for multiple separate operations");
if (node != nullptr) {
const eCustomDataType data_type = eCustomDataType(node->custom1);
b.add_output(data_type, "Standard Deviation"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("The square root of the variance for each group");
b.add_output(data_type, "Variance"_ustr)
.structure_type(StructureType::Field)
.propagate_references()
.description("The expected squared deviation from the mean for each group");
}
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
layout.prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = CD_PROP_FLOAT;
node->custom2 = int16_t(AttrDomain::Point);
}
enum class Operation { StdDev = 0, Variance = 1 };
static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT:
case SOCK_BOOLEAN:
case SOCK_INT:
return CD_PROP_FLOAT;
case SOCK_VECTOR:
case SOCK_RGBA:
return CD_PROP_FLOAT3;
default:
return std::nullopt;
}
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const NodeDeclaration &declaration = *params.node_type().static_declaration;
search_link_ops_for_declarations(params, declaration.inputs);
const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
if (!type) {
return;
}
if (params.in_out() == SOCK_OUT) {
params.add_item(
IFACE_("Standard Deviation"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldVariance"_ustr);
node.custom1 = *type;
params.update_and_connect_available_socket(node, "Standard Deviation"_ustr);
},
0);
params.add_item(
IFACE_("Variance"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldVariance"_ustr);
node.custom1 = *type;
params.update_and_connect_available_socket(node, "Variance"_ustr);
},
-1);
}
else {
params.add_item(
IFACE_("Value"),
[type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeFieldVariance"_ustr);
node.custom1 = *type;
params.update_and_connect_available_socket(node, "Value"_ustr);
},
0);
}
}
class FieldVarianceInput final : public bke::GeometryFieldInput {
private:
GField input_;
Field<int> group_index_;
AttrDomain source_domain_;
Operation operation_;
public:
FieldVarianceInput(const AttrDomain source_domain,
GField input,
Field<int> group_index,
Operation operation)
: bke::GeometryFieldInput(input.cpp_type(), "Calculation"),
input_(std::move(input)),
group_index_(std::move(group_index)),
source_domain_(source_domain),
operation_(operation)
{
}
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
const IndexMask & /*mask*/) const final
{
const AttributeAccessor attributes = *context.attributes();
const int64_t domain_size = attributes.domain_size(source_domain_);
if (domain_size == 0) {
return {};
}
const bke::GeometryFieldContext source_context{context, source_domain_};
fn::FieldEvaluator evaluator{source_context, domain_size};
evaluator.add(input_);
evaluator.add(group_index_);
evaluator.evaluate();
const GVArray g_values = evaluator.get_evaluated(0);
const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
GVArray g_outputs;
g_values.type().to_static_type<int, float, float3>([&]<typename T>() {
const VArraySpan<T> values = g_values.typed<T>();
if (operation_ == Operation::StdDev) {
if (group_indices.is_single()) {
const T mean = std::reduce(values.begin(), values.end(), T()) / domain_size;
const T sum_of_squared_diffs = std::accumulate(
values.begin(), values.end(), T(), [mean](T accumulator, const T &value) {
T difference = mean - value;
return accumulator + difference * difference;
});
g_outputs = VArray<T>::from_single(math::sqrt(sum_of_squared_diffs / domain_size),
domain_size);
}
else {
Map<int, std::pair<T, int>> sum_and_counts;
Map<int, T> deviations;
for (const int i : values.index_range()) {
auto &pair = sum_and_counts.lookup_or_add(group_indices[i], std::make_pair(T(), 0));
pair.first = pair.first + values[i];
pair.second = pair.second + 1;
}
for (const int i : values.index_range()) {
const auto &pair = sum_and_counts.lookup(group_indices[i]);
T mean = pair.first / pair.second;
T deviation = (mean - values[i]);
deviation = deviation * deviation;
T &dev_sum = deviations.lookup_or_add(group_indices[i], T());
dev_sum = dev_sum + deviation;
}
Array<T> outputs(domain_size);
for (const int i : values.index_range()) {
const auto &pair = sum_and_counts.lookup(group_indices[i]);
outputs[i] = math::sqrt(deviations.lookup(group_indices[i]) / pair.second);
}
g_outputs = VArray<T>::from_container(std::move(outputs));
}
}
else {
if (group_indices.is_single()) {
const T mean = std::reduce(values.begin(), values.end(), T()) / domain_size;
const T sum_of_squared_diffs = std::accumulate(
values.begin(), values.end(), T(), [mean](T accumulator, const T &value) {
T difference = mean - value;
return accumulator + difference * difference;
});
g_outputs = VArray<T>::from_single(sum_of_squared_diffs / domain_size, domain_size);
}
else {
Map<int, std::pair<T, int>> sum_and_counts;
Map<int, T> deviations;
for (const int i : values.index_range()) {
auto &pair = sum_and_counts.lookup_or_add(group_indices[i], std::make_pair(T(), 0));
pair.first = pair.first + values[i];
pair.second = pair.second + 1;
}
for (const int i : values.index_range()) {
const auto &pair = sum_and_counts.lookup(group_indices[i]);
T mean = pair.first / pair.second;
T deviation = (mean - values[i]);
deviation = deviation * deviation;
T &dev_sum = deviations.lookup_or_add(group_indices[i], T());
dev_sum = dev_sum + deviation;
}
Array<T> outputs(domain_size);
for (const int i : values.index_range()) {
const auto &pair = sum_and_counts.lookup(group_indices[i]);
outputs[i] = deviations.lookup(group_indices[i]) / pair.second;
}
g_outputs = VArray<T>::from_container(std::move(outputs));
}
}
});
return attributes.adapt_domain(std::move(g_outputs), source_domain_, context.domain());
}
void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const final
{
fn(input_);
fn(group_index_);
}
void hash_unique(UniqueHashBytes &hash, fn::FieldHashDeep &deep_hash_cache) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(deep_hash_cache.ensure(input_));
hash.add(deep_hash_cache.ensure(group_index_));
hash.add(source_domain_);
hash.add(operation_);
}
std::optional<AttrDomain> preferred_domain(
const GeometryComponent & /*component*/) const override
{
return source_domain_;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
const AttrDomain source_domain = AttrDomain(params.node().custom2);
const Field<int> group_index_field = params.extract_input<Field<int>>("Group Index"_ustr);
const GField input_field = params.extract_input<GField>("Value"_ustr);
if (params.output_is_required("Standard Deviation"_ustr)) {
params.set_output<GField>(
"Standard Deviation"_ustr,
GField::from_input<FieldVarianceInput>(
source_domain, input_field, group_index_field, Operation::StdDev));
}
if (params.output_is_required("Variance"_ustr)) {
params.set_output<GField>(
"Variance"_ustr,
GField::from_input<FieldVarianceInput>(
source_domain, input_field, group_index_field, Operation::Variance));
}
}
static void node_rna(StructRNA *srna)
{
static EnumPropertyItem items[] = {
{CD_PROP_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, "Float", "Floating-point value"},
{CD_PROP_FLOAT3,
"FLOAT_VECTOR",
ICON_NODE_SOCKET_VECTOR,
"Vector",
"3D vector with floating-point values"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"Type of data the outputs are calculated from",
items,
NOD_inline_enum_accessors(custom1),
CD_PROP_FLOAT);
RNA_def_node_enum(srna,
"domain",
"Domain",
"",
rna_enum_attribute_domain_items,
NOD_inline_enum_accessors(custom2),
int(AttrDomain::Point),
nullptr,
true);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeFieldVariance"_ustr);
ntype.ui_name = "Field Variance";
ntype.ui_description = "Calculate the standard deviation and variance of a given field";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.geometry_node_execute = node_geo_exec;
ntype.initfunc = node_init;
ntype.draw_buttons = node_layout;
ntype.declare = node_declare;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_field_variance_cc

View File

@@ -0,0 +1,193 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "NOD_geometry_nodes_list.hh"
#include "NOD_geometry_nodes_values.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_enum_types.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "list_function_eval.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_filter_list_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
const bNode *node = b.node_or_null();
if (!node) {
return;
}
const auto type = eNodeSocketDatatype(node->custom1);
b.add_input(type, "List"_ustr).structure_type(StructureType::List).hide_value();
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.description("A field or list representing the values that will not be removed")
.structure_type(StructureType::Dynamic);
b.add_output(type, "Selection"_ustr)
.propagate_all({0})
.structure_type(StructureType::List)
.align_with_previous();
b.add_output(type, "Inverted"_ustr)
.propagate_all({0})
.structure_type(StructureType::List)
.align_with_previous();
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "socket_type", UI_ITEM_NONE, "", ICON_NONE);
}
class SocketSearchOp {
public:
UString socket_name;
eNodeSocketDatatype socket_type;
void operator()(LinkSearchOpParams &params)
{
bNode &node = params.add_node("GeometryNodeFilterList"_ustr);
node.custom1 = socket_type;
params.update_and_connect_available_socket(node, socket_name);
}
};
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(params.other_socket().type);
if (params.in_out() == SOCK_IN) {
if (params.node_tree().typeinfo->validate_link(socket_type, SOCK_BOOLEAN)) {
params.add_item(IFACE_("Selection"), SocketSearchOp{"Selection"_ustr, SOCK_BOOLEAN});
}
params.add_item(IFACE_("List"), SocketSearchOp{"List"_ustr, socket_type});
}
else {
params.add_item(IFACE_("Selection"), SocketSearchOp{"Selection"_ustr, socket_type});
params.add_item(IFACE_("Inverted"), SocketSearchOp{"Inverted"_ustr, socket_type});
}
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(
srna,
"socket_type",
"Socket Type",
"",
rna_enum_node_socket_data_type_items,
NOD_inline_enum_accessors(custom1),
SOCK_FLOAT,
[](bContext * /*C*/, PointerRNA *ptr, PropertyRNA * /*prop*/, bool *r_free) {
*r_free = true;
const bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bke::bNodeTreeType *ntree_type = ntree.typeinfo;
return enum_items_filter(
rna_enum_node_socket_data_type_items, [&](const EnumPropertyItem &item) -> bool {
bke::bNodeSocketType *socket_type = bke::node_socket_type_find_static(item.value);
return ntree_type->valid_socket_type(ntree_type, socket_type);
});
});
}
static GListPtr filter_list(const GListPtr &list, const IndexMask &mask)
{
if (mask.size() == list->size()) {
return list;
}
const CPPType &list_type = list->cpp_type();
return std::visit(
[&]<typename T>(const T &src_data) {
if constexpr (std::is_same_v<T, GList::ArrayData>) {
GArray<> dst_data(list_type, mask.size());
array_utils::gather(GSpan(list_type, src_data.data, list->size()), mask, dst_data);
return GList::from_garray(std::move(dst_data));
}
else if constexpr (std::is_same_v<T, GList::SingleData>) {
return GList::create(list_type, src_data, mask.size());
}
},
list->data());
}
static void output_lists(GeoNodeExecParams &params,
const GListPtr &list,
const IndexMask &selection)
{
if (params.output_is_required("Selection"_ustr)) {
params.set_output("Selection"_ustr, filter_list(list, selection));
}
if (params.output_is_required("Inverted"_ustr)) {
IndexMaskMemory memory;
const IndexMask inverted = selection.complement(IndexRange(list->size()), memory);
params.set_output("Inverted"_ustr, filter_list(list, inverted));
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
GListPtr list = params.extract_input<GListPtr>("List"_ustr);
if (!list) {
params.set_default_remaining_outputs();
return;
}
auto filter_value = params.extract_input<bke::SocketValueVariant>("Selection"_ustr);
if (filter_value.is_single()) {
if (filter_value.get<bool>()) {
output_lists(params, list, IndexMask(list->size()));
}
else {
output_lists(params, list, {});
}
}
else if (filter_value.is_context_dependent_field()) {
ListFieldContext field_context;
fn::FieldEvaluator field_evaluator(field_context, list->size());
field_evaluator.add(filter_value.extract<Field<bool>>());
field_evaluator.evaluate();
output_lists(params, list, field_evaluator.get_evaluated_as_mask(0));
}
else if (filter_value.is_list()) {
const GListPtr keep_list = filter_value.get<GListPtr>();
const VArray<bool> values = keep_list->varray().typed<bool>();
if (values.size() < list->size()) {
params.error_message_add(NodeWarningType::Error, "\"Selection\" list is too small");
params.set_default_remaining_outputs();
return;
}
IndexMaskMemory memory;
output_lists(params, list, IndexMask::from_bools(IndexRange(list->size()), values, memory));
}
else {
params.error_message_add(NodeWarningType::Warning,
"\"Selection\" input must be a field or a list");
params.set_output("Selection"_ustr, std::move(list));
params.set_output("Inverted"_ustr, GList::from_garray(GArray<>(list->cpp_type(), 0)));
params.set_default_remaining_outputs();
}
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeFilterList"_ustr);
ntype.ui_name = "Filter List";
ntype.ui_description = "Remove items from a list";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
ntype.declare = node_declare;
ntype.gather_link_search_ops = node_gather_link_searches;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_filter_list_cc

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_mesh.hh"
#include "GEO_foreach_geometry.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_flip_faces_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Geometry>("Mesh"_ustr)
.supported_type(GeometryComponent::Type::Mesh)
.description("Mesh to flip faces of");
b.add_output<decl::Geometry>("Mesh"_ustr).propagate_all_geometry().align_with_previous();
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field();
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"_ustr);
const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"_ustr);
geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
const bke::MeshFieldContext field_context(*mesh, AttrDomain::Face);
fn::FieldEvaluator evaluator(field_context, mesh->faces_num);
evaluator.add(selection_field);
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_as_mask(0);
if (selection.is_empty()) {
return;
}
bke::mesh_flip_faces(*mesh, selection);
}
});
params.set_output("Mesh"_ustr, std::move(geometry_set));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeFlipFaces"_ustr, GEO_NODE_FLIP_FACES);
ntype.ui_name = "Flip Faces";
ntype.ui_description =
"Reverse the order of the vertices and edges of selected faces, flipping their normal "
"direction";
ntype.enum_name_legacy = "FLIP_FACES";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_flip_faces_cc

View File

@@ -0,0 +1,538 @@
/* 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_blend.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "NOD_socket_search_link.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "BKE_library.hh"
#include "BKE_screen.hh"
#include "WM_api.hh"
#include "fmt/core.h"
namespace blender {
namespace nodes::node_geo_foreach_geometry_element_cc {
/** Shared between zone input and output node. */
static void node_layout_ex(ui::Layout &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_id) {
return;
}
const bool is_zone_input_node = current_node->type_legacy ==
GEO_NODE_FOREACH_GEOMETRY_ELEMENT_INPUT;
bNode &output_node = const_cast<bNode &>(*zone->output_node());
PointerRNA output_node_ptr = RNA_pointer_create_discrete(
current_node_ptr->owner_id, RNA_Node, &output_node);
auto &storage = *static_cast<NodeGeometryForeachGeometryElementOutput *>(output_node.storage);
if (is_zone_input_node) {
if (ui::Layout *panel = layout.panel(C, "input", false, IFACE_("Input Fields"))) {
socket_items::ui::draw_items_list_with_operators<ForeachGeometryElementInputItemsAccessor>(
C, panel, ntree, output_node);
socket_items::ui::draw_active_item_props<ForeachGeometryElementInputItemsAccessor>(
ntree, output_node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
});
}
}
else {
if (ui::Layout *panel = layout.panel(C, "main_items", false, IFACE_("Main Geometry"))) {
socket_items::ui::draw_items_list_with_operators<ForeachGeometryElementMainItemsAccessor>(
C, panel, ntree, output_node);
socket_items::ui::draw_active_item_props<ForeachGeometryElementMainItemsAccessor>(
ntree, output_node, [&](PointerRNA *item_ptr) {
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
});
}
if (ui::Layout *panel = layout.panel(
C, "generation_items", false, IFACE_("Generated Geometry")))
{
socket_items::ui::draw_items_list_with_operators<
ForeachGeometryElementGenerationItemsAccessor>(C, panel, ntree, output_node);
socket_items::ui::draw_active_item_props<ForeachGeometryElementGenerationItemsAccessor>(
ntree, output_node, [&](PointerRNA *item_ptr) {
NodeForeachGeometryElementGenerationItem &active_item =
storage.generation_items.items[storage.generation_items.active_index];
panel->use_property_split_set(true);
panel->use_property_decorate_set(false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
if (active_item.socket_type != SOCK_GEOMETRY) {
panel->prop(item_ptr, "domain", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
});
}
}
layout.prop(&output_node_ptr, "inspection_index", UI_ITEM_NONE, std::nullopt, 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();
b.add_default_layout();
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"_ustr)
.description(
"Index of the element in the source geometry. Note that the same index can occur more "
"than "
"once when iterating over multiple components at once");
b.add_output<decl::Geometry>("Element"_ustr)
.description(
"Single-element geometry for the current iteration. Note that it can be quite "
"inefficient to split up large geometries into many small geometries")
.propagate_all_geometry()
.available(output_storage && AttrDomain(output_storage->domain) != AttrDomain::Corner);
b.add_input<decl::Geometry>("Geometry"_ustr)
.description("Geometry whose elements are iterated over");
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field()
.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 = item.socket_type;
const UString name = item.name ? UString(item.name) : ""_ustr;
const UString identifier(
ForeachGeometryElementInputItemsAccessor::socket_identifier_for_item(item));
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")
.evaluated_geometry_field();
b.add_output(socket_type, name, identifier)
.align_with_previous()
.description("Evaluated field value for the current element");
}
}
b.add_input<decl::Extend>(""_ustr, "__extend__"_ustr)
.structure_type(StructureType::Dynamic)
.custom_draw(
socket_items::ui::draw_extend_socket_fn<ForeachGeometryElementInputItemsAccessor>());
b.add_output<decl::Extend>(""_ustr, "__extend__"_ustr)
.structure_type(StructureType::Dynamic)
.align_with_previous();
}
static void node_layout(ui::Layout &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_discrete(ptr->owner_id, RNA_Node, output_node);
layout.prop(&output_node_ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryForeachGeometryElementInput *data = MEM_new<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, CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, "For Each Element"), label_maxncpy);
}
static bool node_insert_link(bke::NodeInsertLinkParams &params)
{
bNode *output_node = params.ntree.node_by_id(node_storage(params.node).output_node_id);
if (!output_node) {
return true;
}
return socket_items::try_add_item_via_any_extend_socket<
ForeachGeometryElementInputItemsAccessor>(
params.ntree, params.node, *output_node, params.link);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype,
"GeometryNodeForeachGeometryElementInput"_ustr,
GEO_NODE_FOREACH_GEOMETRY_ELEMENT_INPUT);
ntype.ui_name = "For Each Geometry Element Input";
ntype.enum_name_legacy = "FOREACH_GEOMETRY_ELEMENT_INPUT";
ntype.nclass = 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;
bke::node_type_storage(ntype,
"NodeGeometryForeachGeometryElementInput",
node_free_standard_storage,
node_copy_standard_storage);
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"_ustr)
.description(
"The original input geometry with potentially new attributes that are output by the "
"zone");
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 = item.socket_type;
const UString name = item.name ? UString(item.name) : ""_ustr;
const UString 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()
.anonymous_attribute_output({0})
.description("Attribute on the geometry above");
}
b.add_input<decl::Extend>(""_ustr, "__extend__main"_ustr)
.custom_draw(
socket_items::ui::draw_extend_socket_fn<ForeachGeometryElementMainItemsAccessor>());
b.add_output<decl::Extend>(""_ustr, "__extend__main"_ustr).align_with_previous();
auto &panel = b.add_panel("Generated"_ustr);
int previous_output_geometry_index = -1;
int previous_input_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 = item.socket_type;
if (socket_type == SOCK_GEOMETRY && i > 0) {
panel.add_separator();
}
const UString name = item.name ? UString(item.name) : ""_ustr;
const UString identifier(
ForeachGeometryElementGenerationItemsAccessor::socket_identifier_for_item(item));
auto &input_decl = panel.add_input(socket_type, name, identifier)
.socket_name_ptr(
&tree->id,
*ForeachGeometryElementGenerationItemsAccessor::item_srna,
&item,
"name");
auto &output_decl = panel.add_output(socket_type, name, identifier).align_with_previous();
if (socket_type == SOCK_GEOMETRY) {
previous_input_geometry_index = input_decl.index();
previous_output_geometry_index = output_decl.index();
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 {
if (previous_output_geometry_index > 0) {
input_decl.description("Field that will be stored as attribute on the geometry above");
input_decl.evaluated_geometry_field({previous_input_geometry_index});
output_decl.anonymous_attribute_output({previous_output_geometry_index});
}
output_decl.description("Attribute on the geometry above");
}
}
panel.add_input<decl::Extend>(""_ustr, "__extend__generation"_ustr)
.custom_draw(socket_items::ui::draw_extend_socket_fn<
ForeachGeometryElementGenerationItemsAccessor>());
panel.add_output<decl::Extend>(""_ustr, "__extend__generation"_ustr).align_with_previous();
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryForeachGeometryElementOutput *data =
MEM_new<NodeGeometryForeachGeometryElementOutput>(__func__);
data->generation_items.items = MEM_new_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_delete(reinterpret_cast<NodeGeometryForeachGeometryElementOutput *>(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_new<NodeGeometryForeachGeometryElementOutput>(
__func__, dna::shallow_copy(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(bke::NodeInsertLinkParams &params)
{
if (!socket_items::try_add_item_via_any_extend_socket<ForeachGeometryElementMainItemsAccessor>(
params.ntree, params.node, params.node, params.link, "__extend__main"))
{
return false;
}
return socket_items::try_add_item_via_any_extend_socket<
ForeachGeometryElementGenerationItemsAccessor>(
params.ntree, params.node, params.node, params.link);
}
static void node_operators()
{
socket_items::ops::make_common_operators<ForeachGeometryElementInputItemsAccessor>();
socket_items::ops::make_common_operators<ForeachGeometryElementMainItemsAccessor>();
socket_items::ops::make_common_operators<ForeachGeometryElementGenerationItemsAccessor>();
}
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 std::pair<bNode *, bNode *> add_foreach_zone(LinkSearchOpParams &params)
{
bNode &input_node = params.add_node("GeometryNodeForeachGeometryElementInput"_ustr);
bNode &output_node = params.add_node("GeometryNodeForeachGeometryElementOutput"_ustr);
output_node.location[0] = 300;
auto &input_storage = *static_cast<NodeGeometryForeachGeometryElementInput *>(
input_node.storage);
input_storage.output_node_id = output_node.identifier;
return {&input_node, &output_node};
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const bNodeSocket &other_socket = params.other_socket();
const eNodeSocketDatatype type = other_socket.type;
if (type != SOCK_GEOMETRY) {
return;
}
if (other_socket.in_out == SOCK_OUT) {
params.add_item_full_name(IFACE_("For Each Element"), [](LinkSearchOpParams &params) {
const auto [input_node, output_node] = add_foreach_zone(params);
params.update_and_connect_available_socket(*input_node, "Geometry"_ustr);
});
}
else {
params.add_item_full_name(
fmt::format(fmt::runtime(IFACE_("For Each Element {} Main")), UI_MENU_ARROW_SEP),
[](LinkSearchOpParams &params) {
const auto [input_node, output_node] = add_foreach_zone(params);
socket_items::clear<ForeachGeometryElementGenerationItemsAccessor>(*output_node);
params.update_and_connect_available_socket(*output_node, "Geometry"_ustr);
});
params.add_item_full_name(
fmt::format(fmt::runtime(IFACE_("For Each Element {} Generated")), UI_MENU_ARROW_SEP),
[](LinkSearchOpParams &params) {
const auto [input_node, output_node] = add_foreach_zone(params);
params.node_tree.ensure_topology_cache();
bke::node_add_link(params.node_tree,
*output_node,
output_node->output_socket(2),
params.node,
params.socket);
});
}
}
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
{
socket_items::blend_write<ForeachGeometryElementInputItemsAccessor>(&writer, node);
socket_items::blend_write<ForeachGeometryElementGenerationItemsAccessor>(&writer, node);
socket_items::blend_write<ForeachGeometryElementMainItemsAccessor>(&writer, node);
}
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
{
socket_items::blend_read_data<ForeachGeometryElementInputItemsAccessor>(&reader, node);
socket_items::blend_read_data<ForeachGeometryElementMainItemsAccessor>(&reader, node);
socket_items::blend_read_data<ForeachGeometryElementGenerationItemsAccessor>(&reader, node);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype,
"GeometryNodeForeachGeometryElementOutput"_ustr,
GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT);
ntype.ui_name = "For Each Geometry Element Output";
ntype.enum_name_legacy = "FOREACH_GEOMETRY_ELEMENT_OUTPUT";
ntype.nclass = 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.gather_link_search_ops = node_gather_link_searches;
ntype.get_extra_info = node_extra_info;
ntype.no_muting = true;
ntype.blend_write_storage_content = node_blend_write;
ntype.blend_data_read_storage_content = node_blend_read;
bke::node_type_storage(
ntype, "NodeGeometryForeachGeometryElementOutput", node_free_storage, node_copy_storage);
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace output_node
} // namespace nodes::node_geo_foreach_geometry_element_cc
namespace nodes {
StructRNA **ForeachGeometryElementInputItemsAccessor::item_srna =
&RNA_ForeachGeometryElementInputItem;
void ForeachGeometryElementInputItemsAccessor::blend_write_item(BlendWriter *writer,
const ItemT &item)
{
writer->write_string(item.name);
}
void ForeachGeometryElementInputItemsAccessor::blend_read_data_item(BlendDataReader *reader,
ItemT &item)
{
BLO_read_string(reader, &item.name);
}
StructRNA **ForeachGeometryElementMainItemsAccessor::item_srna =
&RNA_ForeachGeometryElementMainItem;
void ForeachGeometryElementMainItemsAccessor::blend_write_item(BlendWriter *writer,
const ItemT &item)
{
writer->write_string(item.name);
}
void ForeachGeometryElementMainItemsAccessor::blend_read_data_item(BlendDataReader *reader,
ItemT &item)
{
BLO_read_string(reader, &item.name);
}
StructRNA **ForeachGeometryElementGenerationItemsAccessor::item_srna =
&RNA_ForeachGeometryElementGenerationItem;
void ForeachGeometryElementGenerationItemsAccessor::blend_write_item(BlendWriter *writer,
const ItemT &item)
{
writer->write_string(item.name);
}
void ForeachGeometryElementGenerationItemsAccessor::blend_read_data_item(BlendDataReader *reader,
ItemT &item)
{
BLO_read_string(reader, &item.name);
}
} // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_instances.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_geometry_to_instance_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry"_ustr)
.multi_input()
.description("Each input geometry is turned into a separate instance");
b.add_output<decl::Geometry>("Instances"_ustr).propagate_all_geometry();
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeoNodesMultiInput<GeometrySet> geometries =
params.extract_input<GeoNodesMultiInput<GeometrySet>>("Geometry"_ustr);
auto instances = std::make_unique<bke::Instances>(geometries.values.size());
MutableSpan<int> handles = instances->reference_handles_for_write();
for (const int i : geometries.values.index_range()) {
GeometrySet &geometry = geometries.values[i];
geometry.ensure_owns_direct_data();
handles[i] = instances->add_reference(std::move(geometry));
}
instances->transforms_for_write().fill(float4x4::identity());
params.set_output("Instances"_ustr, GeometrySet::from_instances(std::move(instances)));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGeometryToInstance"_ustr, GEO_NODE_GEOMETRY_TO_INSTANCE);
ntype.ui_name = "Geometry to Instance";
ntype.ui_description =
"Convert each input geometry into an instance, which can be much faster than the Join "
"Geometry node when the inputs are large";
ntype.enum_name_legacy = "GEOMETRY_TO_INSTANCE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.default_width = bke::NodeWidth::_160;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_geometry_to_instance_cc

View File

@@ -0,0 +1,101 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute.hh"
#include "BLI_sort.hh"
#include "RNA_enum_types.hh"
#include "NOD_geometry_nodes_list.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_get_attribute_names_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_output<decl::String>("Names"_ustr).structure_type(StructureType::List);
b.add_input<decl::Geometry>("Geometry"_ustr);
{
auto &p = b.add_panel("Filter Data Type"_ustr);
p.add_input<decl::Bool>("Filter Data Type"_ustr).panel_toggle();
p.add_input<decl::Menu>("Data Type"_ustr)
.static_items(rna_enum_attrtype_items)
.default_value(bke::AttrType::Float)
.optional_label()
.usage_by_panel_toggle();
}
{
auto &p = b.add_panel("Filter Domain"_ustr);
p.add_input<decl::Bool>("Filter Domain"_ustr).panel_toggle();
p.add_input<decl::Menu>("Domain"_ustr)
.static_items(rna_enum_attribute_domain_items)
.default_value(AttrDomain::Point)
.optional_label()
.usage_by_panel_toggle();
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
const GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"_ustr);
const bool filter_data_type = params.extract_input<bool>("Filter Data Type"_ustr);
const bool filter_domain = params.extract_input<bool>("Filter Domain"_ustr);
const bke::AttrType data_type = params.extract_input<bke::AttrType>("Data Type"_ustr);
const AttrDomain domain = params.extract_input<AttrDomain>("Domain"_ustr);
VectorSet<std::string> names_set;
for (const GeometryComponent *component : geometry_set.get_components()) {
const std::optional<AttributeAccessor> attributes = component->attributes();
if (!attributes) {
continue;
}
attributes->foreach_attribute([&](const AttributeIter &iter) {
if (filter_data_type) {
if (iter.data_type != data_type) {
return;
}
}
if (filter_domain) {
if (iter.domain != domain) {
return;
}
}
if (iter.name.startswith(".")) {
return;
}
names_set.add(iter.name);
});
}
Vector<std::string> names = names_set.extract_vector();
parallel_sort(
names.begin(), names.end(), [](const StringRef a, const StringRef b) { return a < b; });
params.set_output("Names"_ustr, GList::from_container(names));
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGetAttributeNames"_ustr);
ntype.ui_name = "Get Attribute Names";
ntype.ui_description = "Retrieves attribute names as a list of strings";
ntype.nclass = NODE_CLASS_ATTRIBUTE;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
ntype.default_width = bke::NodeWidth::_160;
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_get_attribute_names_cc

View File

@@ -0,0 +1,183 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "NOD_geo_bundle.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_rna_define.hh"
#include "RNA_enum_types.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include <fmt/format.h>
namespace blender::nodes::node_geo_get_bundle_item_cc {
NODE_STORAGE_FUNCS(NodeGetBundleItem)
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
const bNode *node = b.node_or_null();
b.add_input<decl::Bundle>("Bundle"_ustr);
b.add_output<decl::Bundle>("Bundle"_ustr).align_with_previous().propagate_all();
if (node != nullptr) {
const NodeGetBundleItem &storage = node_storage(*node);
const eNodeSocketDatatype socket_type = storage.socket_type;
auto &decl = b.add_output(socket_type, "Item"_ustr).propagate_all();
if (storage.structure_type == NodeSocketInterfaceStructureType::Auto) {
decl.structure_type(StructureType::Dynamic);
}
else {
decl.structure_type(StructureType(storage.structure_type));
}
}
b.add_output<decl::Bool>("Exists"_ustr);
b.add_input<decl::String>("Path"_ustr).optional_label();
b.add_input<decl::Bool>("Remove"_ustr);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
layout.prop(ptr, "socket_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_layout_ex(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
layout.prop(ptr, "structure_type", UI_ITEM_NONE, IFACE_("Shape"), ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
auto *storage = MEM_new<NodeGetBundleItem>(__func__);
storage->socket_type = SOCK_FLOAT;
node->storage = storage;
}
static void node_geo_exec(GeoNodeExecParams params)
{
const bNode &node = params.node();
const NodeGetBundleItem &storage = node_storage(node);
nodes::BundlePtr bundle = params.extract_input<nodes::BundlePtr>("Bundle"_ustr);
if (!bundle) {
params.set_default_remaining_outputs();
return;
}
const std::string path = params.extract_input<std::string>("Path"_ustr);
const bool remove = params.extract_input<bool>("Remove"_ustr);
if (!Bundle::is_valid_path(path)) {
if (!path.empty()) {
params.error_message_add(NodeWarningType::Warning, "Invalid bundle path");
}
params.set_output("Bundle"_ustr, std::move(bundle));
params.set_default_remaining_outputs();
return;
}
const BundleItemValue *value = bundle->lookup_path(path);
if (!value) {
if (!params.output_is_required("Exists"_ustr)) {
params.error_message_add(NodeWarningType::Warning, "Bundle path not found");
}
params.set_output("Bundle"_ustr, std::move(bundle));
params.set_default_remaining_outputs();
return;
}
const auto *socket_value = std::get_if<BundleItemSocketValue>(&value->value);
if (!socket_value) {
params.error_message_add(
NodeWarningType::Error,
fmt::format("{}: \"{}\"", TIP_("Cannot get internal value from bundle"), path));
params.set_output("Bundle"_ustr, std::move(bundle));
params.set_default_remaining_outputs();
return;
}
const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(storage.socket_type, 0);
SocketValueVariant output_value = socket_value->value;
if (socket_value->type->type != stype->type) {
if (std::optional<SocketValueVariant> converted_value = implicitly_convert_socket_value(
*socket_value->type, output_value, *stype))
{
output_value = std::move(*converted_value);
}
else {
params.error_message_add(NodeWarningType::Error,
"Cannot implicitly convert item to the selected type");
params.set_output("Bundle"_ustr, std::move(bundle));
params.set_default_remaining_outputs();
return;
}
}
if (remove) {
bundle.ensure_mutable_inplace().remove_path(path);
}
params.set_output("Bundle"_ustr, std::move(bundle));
params.set_output("Item"_ustr, std::move(output_value));
params.set_output("Exists"_ustr, true);
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"socket_type",
"Socket Type",
"Value may be implicitly converted if the type does not match",
rna_enum_node_socket_data_type_items,
NOD_storage_enum_accessors(socket_type),
SOCK_FLOAT,
[](bContext * /*C*/, PointerRNA *ptr, PropertyRNA * /*prop*/, bool *r_free) {
*r_free = true;
const bNodeTree &ntree = *id_cast<const bNodeTree *>(ptr->owner_id);
return enum_items_filter(rna_enum_node_socket_data_type_items,
[&](const EnumPropertyItem &item) -> bool {
return socket_type_supported_in_bundle(
eNodeSocketDatatype(item.value), ntree.type);
});
});
RNA_def_node_enum(srna,
"structure_type",
"Structure Type",
"What kind of higher order types are expected to flow through this socket",
rna_enum_node_socket_structure_type_items,
NOD_storage_enum_accessors(structure_type));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "NodeGetBundleItem"_ustr);
ntype.ui_name = "Get Bundle Item";
ntype.ui_description = "Retrieve a bundle item by path.";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
ntype.draw_buttons_ex = node_layout_ex;
bke::node_type_storage(
ntype, "NodeGetBundleItem", node_free_standard_storage, node_copy_standard_storage);
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_get_bundle_item_cc

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_socket_search_link.hh"
namespace blender::nodes::node_geo_get_geometry_bundle {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Geometry>("Geometry"_ustr).description("Geometry to get the bundle of");
b.add_output<decl::Geometry>("Geometry"_ustr).propagate_all().align_with_previous();
b.add_output<decl::Bundle>("Bundle"_ustr).propagate_all();
b.add_input<decl::Bool>("Remove"_ustr)
.default_value(false)
.description(
"Removing the bundle from the geometry can be beneficial to avoid unnecessary data "
"copies");
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"_ustr);
const bool remove = params.extract_input<bool>("Remove"_ustr);
BundlePtr bundle;
if (remove) {
bundle = std::move(geometry_set.bundle_ptr());
}
else {
bundle = geometry_set.bundle_ptr();
}
params.set_output("Geometry"_ustr, std::move(geometry_set));
params.set_output("Bundle"_ustr, std::move(bundle));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGetGeometryBundle"_ustr);
ntype.ui_name = "Get Geometry Bundle";
ntype.ui_description = "Get the bundle of a geometry";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
ntype.default_width = bke::NodeWidth::_160;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_get_geometry_bundle

View File

@@ -0,0 +1,84 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "RNA_enum_types.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_get_geometry_component_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Geometry>("Geometry"_ustr);
b.add_output<decl::Geometry>("Geometry"_ustr)
.align_with_previous()
.description("The input geometry with optionally the selected component removed")
.propagate_all_geometry();
b.add_output<decl::Geometry>("Component"_ustr).propagate_all_geometry();
b.add_output<decl::Bool>("Exists"_ustr)
.description(
"Whether the geometry had a component of the type. This does not check if the component "
"is empty");
b.add_input<decl::Menu>("Type"_ustr)
.static_items(rna_enum_geometry_component_type_items)
.optional_label();
b.add_input<decl::Bool>("Remove"_ustr).default_value(true);
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry = params.extract_input<GeometrySet>("Geometry"_ustr);
const GeometryComponent::Type type = params.extract_input<GeometryComponent::Type>("Type"_ustr);
const bool remove = params.extract_input<bool>("Remove"_ustr);
GeometrySet component_geo;
bool exists = false;
switch (type) {
case bke::GeometryComponent::Type::Mesh:
case bke::GeometryComponent::Type::PointCloud:
case bke::GeometryComponent::Type::Instance:
case bke::GeometryComponent::Type::Volume:
case bke::GeometryComponent::Type::Curve:
case bke::GeometryComponent::Type::GreasePencil: {
if (const GeometryComponent *component = geometry.get_component(type)) {
component_geo.add(*component);
exists = true;
}
component_geo.copy_bundle_from(geometry);
component_geo.set_name(geometry.name());
if (remove) {
geometry.remove(type);
}
break;
}
case bke::GeometryComponent::Type::Edit: {
break;
}
}
params.set_output("Geometry"_ustr, geometry);
params.set_output("Component"_ustr, component_geo);
params.set_output("Exists"_ustr, exists);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGetGeometryComponent"_ustr);
ntype.ui_name = "Get Geometry Component";
ntype.ui_description = "Get a single component of a geometry";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.default_width = bke::NodeWidth::_180;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_get_geometry_component_cc

View File

@@ -0,0 +1,158 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "BKE_volume.hh"
#include "BKE_volume_grid.hh"
#include "BLT_translation.hh"
#include "RNA_enum_types.hh"
#include "NOD_rna_define.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_geo_get_named_grid_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Volume"_ustr).description("Volume to take a named grid out of");
b.add_input<decl::String>("Name"_ustr).optional_label().is_volume_grid_name();
b.add_input<decl::Bool>("Remove"_ustr)
.default_value(true)
.translation_context(BLT_I18NCONTEXT_OPERATOR_DEFAULT);
b.add_output<decl::Geometry>("Volume"_ustr);
const bNode *node = b.node_or_null();
if (!node) {
return;
}
b.add_output(eNodeSocketDatatype(node->custom1), "Grid"_ustr)
.structure_type(StructureType::Grid);
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
}
#ifdef WITH_OPENVDB
static std::optional<SocketValueVariant> try_get_named_grid(
GeoNodeExecParams &params,
Volume &volume,
const StringRef grid_name,
const bool remove_grid,
const eNodeSocketDatatype desired_socket_type)
{
const bke::VolumeGridData *grid = BKE_volume_grid_find(&volume, grid_name);
if (!grid) {
return std::nullopt;
}
const VolumeGridType stored_grid_type = grid->grid_type();
const std::optional<eNodeSocketDatatype> current_socket_type = bke::grid_type_to_socket_type(
stored_grid_type);
if (!current_socket_type) {
return std::nullopt;
}
/* Increment user count before removing from volume. */
grid->add_user();
if (remove_grid) {
BKE_volume_grid_remove(&volume, grid);
}
SocketValueVariant value_variant = SocketValueVariant::From(bke::GVolumeGrid(grid));
if (current_socket_type != desired_socket_type) {
std::optional<SocketValueVariant> converted_value = implicitly_convert_socket_value(
*bke::node_socket_type_find_static(*current_socket_type),
value_variant,
*bke::node_socket_type_find_static(desired_socket_type));
if (!converted_value) {
return std::nullopt;
}
params.error_message_add(NodeWarningType::Info, "Implicit grid type conversion");
value_variant = std::move(*converted_value);
}
return value_variant;
}
#endif
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
const bNode &node = params.node();
GeometrySet geometry_set = params.extract_input<GeometrySet>("Volume"_ustr);
const std::string grid_name = params.extract_input<std::string>("Name"_ustr);
const bool remove_grid = params.extract_input<bool>("Remove"_ustr);
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(node.custom1);
if (Volume *volume = geometry_set.get_volume_for_write()) {
if (std::optional<SocketValueVariant> value_variant = try_get_named_grid(
params, *volume, grid_name, remove_grid, socket_type))
{
params.set_output("Grid"_ustr, std::move(*value_variant));
params.set_output("Volume"_ustr, std::move(geometry_set));
return;
}
}
if (!grid_name.empty()) {
params.error_message_add(NodeWarningType::Warning,
"No supported grid found with the given name");
}
if (std::optional<VolumeGridType> grid_type = bke::socket_type_to_grid_type(socket_type)) {
params.set_output("Grid"_ustr, bke::GVolumeGrid(*grid_type));
params.set_output("Volume"_ustr, std::move(geometry_set));
return;
}
params.set_output("Volume"_ustr, std::move(geometry_set));
params.set_default_remaining_outputs();
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = SOCK_FLOAT;
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"Node socket data type",
rna_enum_node_socket_data_type_items,
NOD_inline_enum_accessors(custom1),
SOCK_FLOAT,
grid_socket_type_items_filter_fn);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGetNamedGrid"_ustr, GEO_NODE_GET_NAMED_GRID);
ntype.ui_name = "Get Named Grid";
ntype.ui_description = "Get volume grid from a volume geometry with the specified name";
ntype.enum_name_legacy = "GET_NAMED_GRID";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.initfunc = node_init;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_get_named_grid_cc

View File

@@ -0,0 +1,125 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_geo_bundle.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_geometry_nodes_list.hh"
#include "NOD_string_pattern.hh"
#include "RNA_enum_types.hh"
#include "UI_resources.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_get_nested_bundle_paths_cc {
enum class Mode {
All = 0,
BundleType = 1,
DataType = 2,
};
static const EnumPropertyItem mode_items[] = {
{int(Mode::All), "ALL", ICON_NONE, "All", "Output all paths"},
{int(Mode::BundleType),
"BUNDLE_TYPE",
ICON_NONE,
N_("Bundle Type"),
N_("Find paths to bundles of a specific type")},
{int(Mode::DataType),
"DATA_TYPE",
ICON_NONE,
N_("Data Type"),
N_("Find paths to values of a specific data type")},
{0},
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Bundle>("Bundle"_ustr);
b.add_input<decl::Menu>("Mode"_ustr).static_items(mode_items).optional_label();
b.add_input<decl::Menu>("Pattern Mode"_ustr)
.static_items(string_pattern_mode_items)
.optional_label()
.usage_by_menu("Mode"_ustr, int(Mode::BundleType));
b.add_input<decl::String>("Bundle Type"_ustr)
.optional_label()
.usage_by_menu("Mode"_ustr, int(Mode::BundleType));
b.add_input<decl::Menu>("Data Type"_ustr)
.static_items(rna_enum_node_socket_data_type_items,
[](const EnumPropertyItem &item) {
return socket_type_supported_in_bundle(eNodeSocketDatatype(item.value),
NTREE_GEOMETRY);
})
.optional_label()
.usage_by_menu("Mode"_ustr, int(Mode::DataType));
b.add_output<decl::String>("Paths"_ustr).structure_type(StructureType::List);
}
static void node_geo_exec(GeoNodeExecParams params)
{
BundlePtr bundle_ptr = params.extract_input<BundlePtr>("Bundle"_ustr);
const Mode mode = params.extract_input<Mode>("Mode"_ustr);
if (!bundle_ptr) {
params.set_default_remaining_outputs();
return;
}
const Bundle &bundle = *bundle_ptr;
Vector<std::string> paths;
switch (mode) {
case Mode::All: {
foreach_nested_bundle_item(
bundle, [&](const Span<BundleKey> path, const BundleItemValue & /*value*/) {
paths.append(Bundle::combine_path(path));
});
break;
}
case Mode::BundleType: {
const std::string type_pattern = params.extract_input<std::string>("Bundle Type"_ustr);
const StringPatternMode pattern_mode = params.extract_input<StringPatternMode>(
"Pattern Mode"_ustr);
std::string pattern_error;
std::optional<StringPattern> pattern_fn = StringPattern::from_str(
pattern_mode, type_pattern, pattern_error);
if (!pattern_fn) {
params.error_message_add(NodeWarningType::Error, pattern_error);
break;
}
if (!type_pattern.empty()) {
paths = gather_bundle_paths_by_bundle_type(
bundle, [&](const StringRef type) { return pattern_fn->match(type); });
}
break;
}
case Mode::DataType:
const eNodeSocketDatatype data_type = params.extract_input<eNodeSocketDatatype>(
"Data Type"_ustr);
paths = gather_bundle_paths_by_data_type(bundle, data_type);
break;
}
/* Make sure the order is deterministic and doesn't depend on hash tables in the bundle. */
std::ranges::sort(paths);
params.set_output("Paths"_ustr, GList::from_container(std::move(paths)));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "NodeGetNestedBundlePaths"_ustr);
ntype.ui_name = "Get Nested Bundle Paths";
ntype.ui_description = "Get paths to items in a nested bundle with a filter";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
ntype.default_width = bke::NodeWidth::_180;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_get_nested_bundle_paths_cc

View File

@@ -0,0 +1,71 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "NOD_rna_define.hh"
#include "RNA_enum_types.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_geo_gizmo_dial_cc {
NODE_STORAGE_FUNCS(NodeGeometryDialGizmo)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>("Value"_ustr).hide_value().multi_input();
b.add_input<decl::Vector>("Position"_ustr).subtype(PROP_TRANSLATION);
b.add_input<decl::Vector>("Up"_ustr).default_value({0, 0, 1}).subtype(PROP_XYZ);
b.add_input<decl::Bool>("Screen Space"_ustr)
.default_value(true)
.description(
"If true, the gizmo is displayed in screen space. Otherwise it's in object space");
b.add_input<decl::Float>("Radius"_ustr).default_value(1.0f);
b.add_output<decl::Geometry>("Transform"_ustr);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryDialGizmo *storage = MEM_new<NodeGeometryDialGizmo>(__func__);
node->storage = storage;
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "color_id", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"color_id",
"Color",
"",
rna_enum_geometry_nodes_gizmo_color_items,
NOD_storage_enum_accessors(color_id));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGizmoDial"_ustr, GEO_NODE_GIZMO_DIAL);
ntype.ui_name = "Dial Gizmo";
ntype.ui_description = "Show a dial gizmo in the viewport for a value";
ntype.enum_name_legacy = "GIZMO_DIAL";
ntype.nclass = NODE_CLASS_INTERFACE;
bke::node_type_storage(
ntype, "NodeGeometryDialGizmo", node_free_standard_storage, node_copy_standard_storage);
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.initfunc = node_init;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_gizmo_dial_cc

View File

@@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "NOD_rna_define.hh"
#include "RNA_enum_types.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_geo_gizmo_linear_cc {
NODE_STORAGE_FUNCS(NodeGeometryLinearGizmo)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>("Value"_ustr).hide_value().multi_input();
b.add_input<decl::Vector>("Position"_ustr).subtype(PROP_TRANSLATION);
b.add_input<decl::Vector>("Direction"_ustr).default_value({0, 0, 1}).subtype(PROP_XYZ);
b.add_output<decl::Geometry>("Transform"_ustr);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryLinearGizmo *storage = MEM_new<NodeGeometryLinearGizmo>(__func__);
node->storage = storage;
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "color_id", UI_ITEM_NONE, "", ICON_NONE);
layout.prop(ptr, "draw_style", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_rna(StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_node_enum(srna,
"color_id",
"Color",
"",
rna_enum_geometry_nodes_gizmo_color_items,
NOD_storage_enum_accessors(color_id));
prop = RNA_def_node_enum(srna,
"draw_style",
"Draw Style",
"",
rna_enum_geometry_nodes_linear_gizmo_draw_style_items,
NOD_storage_enum_accessors(draw_style));
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_NODETREE);
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGizmoLinear"_ustr, GEO_NODE_GIZMO_LINEAR);
ntype.ui_name = "Linear Gizmo";
ntype.ui_description = "Show a linear gizmo in the viewport for a value";
ntype.enum_name_legacy = "GIZMO_LINEAR";
ntype.nclass = NODE_CLASS_INTERFACE;
bke::node_type_storage(
ntype, "NodeGeometryLinearGizmo", node_free_standard_storage, node_copy_standard_storage);
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.initfunc = node_init;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_gizmo_linear_cc

View File

@@ -0,0 +1,82 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "RNA_enum_types.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender::nodes::node_geo_gizmo_transform_cc {
NODE_STORAGE_FUNCS(NodeGeometryTransformGizmo)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Matrix>("Value"_ustr).multi_input();
b.add_input<decl::Vector>("Position"_ustr).subtype(PROP_TRANSLATION);
b.add_input<decl::Rotation>("Rotation"_ustr)
.description(
"Local rotation of the gizmo. Only used if the local transforms are used in the 3D "
"view");
b.add_output<decl::Geometry>("Transform"_ustr);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryTransformGizmo *storage = MEM_new<NodeGeometryTransformGizmo>(__func__);
storage->flag = (GEO_NODE_TRANSFORM_GIZMO_USE_TRANSLATION_X |
GEO_NODE_TRANSFORM_GIZMO_USE_TRANSLATION_Y |
GEO_NODE_TRANSFORM_GIZMO_USE_TRANSLATION_Z |
GEO_NODE_TRANSFORM_GIZMO_USE_ROTATION_X |
GEO_NODE_TRANSFORM_GIZMO_USE_ROTATION_Y |
GEO_NODE_TRANSFORM_GIZMO_USE_ROTATION_Z | GEO_NODE_TRANSFORM_GIZMO_USE_SCALE_X |
GEO_NODE_TRANSFORM_GIZMO_USE_SCALE_Y | GEO_NODE_TRANSFORM_GIZMO_USE_SCALE_Z);
node->storage = storage;
}
static void node_layout_ex(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
{
ui::Layout &row = layout.column(true, IFACE_("Translation"));
row.prop(ptr, "use_translation_x", UI_ITEM_NONE, IFACE_("X"), ICON_NONE);
row.prop(ptr, "use_translation_y", UI_ITEM_NONE, IFACE_("Y"), ICON_NONE);
row.prop(ptr, "use_translation_z", UI_ITEM_NONE, IFACE_("Z"), ICON_NONE);
}
{
ui::Layout &row = layout.column(true, IFACE_("Rotation"));
row.prop(ptr, "use_rotation_x", UI_ITEM_NONE, IFACE_("X"), ICON_NONE);
row.prop(ptr, "use_rotation_y", UI_ITEM_NONE, IFACE_("Y"), ICON_NONE);
row.prop(ptr, "use_rotation_z", UI_ITEM_NONE, IFACE_("Z"), ICON_NONE);
}
{
ui::Layout &row = layout.column(true, IFACE_("Scale"));
row.prop(ptr, "use_scale_x", UI_ITEM_NONE, IFACE_("X"), ICON_NONE);
row.prop(ptr, "use_scale_y", UI_ITEM_NONE, IFACE_("Y"), ICON_NONE);
row.prop(ptr, "use_scale_z", UI_ITEM_NONE, IFACE_("Z"), ICON_NONE);
}
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGizmoTransform"_ustr, GEO_NODE_GIZMO_TRANSFORM);
ntype.ui_name = "Transform Gizmo";
ntype.ui_description = "Show a transform gizmo in the viewport";
ntype.enum_name_legacy = "GIZMO_TRANSFORM";
ntype.nclass = NODE_CLASS_INTERFACE;
bke::node_type_storage(
ntype, "NodeGeometryTransformGizmo", node_free_standard_storage, node_copy_standard_storage);
ntype.declare = node_declare;
ntype.draw_buttons_ex = node_layout_ex;
ntype.initfunc = node_init;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_gizmo_transform_cc

View File

@@ -0,0 +1,166 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "GEO_realize_instances.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_grease_pencil_to_curves_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Grease Pencil"_ustr)
.supported_type(bke::GeometryComponent::Type::GreasePencil)
.description("Grease Pencil data to convert to curves");
b.add_input<decl::Bool>("Selection"_ustr)
.default_value(true)
.hide_value()
.evaluated_geometry_field()
.description("Select the layers to convert");
b.add_input<decl::Bool>("Layers as Instances"_ustr)
.default_value(true)
.description("Create a separate curve instance for every layer");
b.add_output<decl::Geometry>("Curves"_ustr).propagate_all_geometry();
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet grease_pencil_geometry = params.extract_input<GeometrySet>("Grease Pencil"_ustr);
const GreasePencil *grease_pencil = grease_pencil_geometry.get_grease_pencil();
if (!grease_pencil) {
params.set_default_remaining_outputs();
return;
}
const Span<const bke::greasepencil::Layer *> layers = grease_pencil->layers();
const int layers_num = layers.size();
const bke::GreasePencilFieldContext field_context{*grease_pencil};
const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"_ustr);
FieldEvaluator evaluator{field_context, layers_num};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask layer_selection = evaluator.get_evaluated_selection_as_mask();
const int instances_num = layer_selection.size();
if (instances_num == 0) {
params.set_default_remaining_outputs();
return;
}
auto instances = std::make_unique<bke::Instances>(layer_selection.size());
MutableSpan<int> handles = instances->reference_handles_for_write();
MutableSpan<float4x4> transforms = instances->transforms_for_write();
std::optional<int> empty_geometry_handle;
layer_selection.foreach_index([&](const int layer_i, const int pos) {
const bke::greasepencil::Layer &layer = *layers[layer_i];
const bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(layer);
const float4x4 transform = layer.local_transform();
if (!drawing) {
if (!empty_geometry_handle.has_value()) {
empty_geometry_handle = instances->add_reference(bke::InstanceReference());
}
handles[pos] = *empty_geometry_handle;
transforms[pos] = transform;
return;
}
const bke::CurvesGeometry &layer_strokes = drawing->strokes();
Curves *curves_id = bke::curves_new_nomain(layer_strokes);
curves_id->mat = MEM_dupalloc(grease_pencil->material_array);
curves_id->totcol = grease_pencil->material_array_num;
GeometrySet curves_geometry = GeometrySet::from_curves(curves_id);
curves_geometry.set_name(layer.name());
handles[pos] = instances->add_reference(std::move(curves_geometry));
transforms[pos] = transform;
});
const bke::AttributeAccessor grease_pencil_attributes = grease_pencil->attributes();
bke::MutableAttributeAccessor instances_attributes = instances->attributes_for_write();
grease_pencil_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (ELEM(iter.name, "opacity")) {
return;
}
if (iter.data_type == bke::AttrType::String) {
return;
}
const GAttributeReader src_attribute = iter.get();
if (!src_attribute) {
return;
}
if (src_attribute.varray.is_span() && src_attribute.sharing_info) {
/* Try reusing existing attribute array. */
instances_attributes.add(
iter.name,
AttrDomain::Instance,
iter.data_type,
bke::AttributeInitShared{src_attribute.varray.get_internal_span().data(),
*src_attribute.sharing_info});
return;
}
if (!instances_attributes.add(
iter.name, AttrDomain::Instance, iter.data_type, bke::AttributeInitConstruct()))
{
return;
}
bke::GSpanAttributeWriter dst_attribute = instances_attributes.lookup_for_write_span(
iter.name);
array_utils::gather(src_attribute.varray, layer_selection, dst_attribute.span);
dst_attribute.finish();
});
{
/* Manually propagate "opacity" data, because it's not a layer attribute on grease pencil
* yet. */
if (SpanAttributeWriter<float> opacity_attribute =
instances_attributes.lookup_or_add_for_write_only_span<float>("opacity",
AttrDomain::Instance))
{
layer_selection.foreach_index([&](const int layer_i, const int instance_i) {
opacity_attribute.span[instance_i] = grease_pencil->layer(layer_i).opacity;
});
opacity_attribute.finish();
}
}
GeometrySet curves_geometry = GeometrySet::from_instances(std::move(instances));
curves_geometry.set_name(grease_pencil_geometry.name());
curves_geometry.copy_bundle_from(grease_pencil_geometry);
const bool layers_as_instances = params.extract_input<bool>("Layers as Instances"_ustr);
if (!layers_as_instances) {
geometry::RealizeInstancesOptions options;
const NodeAttributeFilter attribute_filter = params.get_attribute_filter("Curves"_ustr);
options.attribute_filter = attribute_filter;
curves_geometry = geometry::realize_instances(curves_geometry, options).geometry;
}
params.set_output("Curves"_ustr, std::move(curves_geometry));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, "GeometryNodeGreasePencilToCurves"_ustr, GEO_NODE_GREASE_PENCIL_TO_CURVES);
ntype.ui_name = "Grease Pencil to Curves";
ntype.ui_description = "Convert Grease Pencil layers into curve instances";
ntype.enum_name_legacy = "GREASE_PENCIL_TO_CURVES";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
ntype.default_width = bke::NodeWidth::_180;
bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_grease_pencil_to_curves_cc

View File

@@ -0,0 +1,347 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_enum_types.hh"
#include "node_geometry_util.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "BKE_volume_grid.hh"
#include "BKE_volume_openvdb.hh"
#ifdef WITH_OPENVDB
# include "openvdb/tools/VolumeAdvect.h"
#endif
namespace blender::nodes::node_geo_grid_advect_cc {
enum class IntegrationScheme : int8_t {
SemiLagrangian = 0,
Midpoint = 1,
RungeKutta3 = 2,
RungeKutta4 = 3,
MacCormack = 4,
BFECC = 5,
};
enum class LimiterType : int8_t {
None = 0,
Clamp = 1,
Revert = 2,
};
static const EnumPropertyItem integration_scheme_items[] = {
{int(IntegrationScheme::SemiLagrangian),
"SEMI",
0,
N_("Semi-Lagrangian"),
N_("1st order semi-Lagrangian integration. Fast but least accurate, suitable for simple "
"advection")},
{int(IntegrationScheme::Midpoint),
"MID",
0,
N_("Midpoint"),
N_("2nd order midpoint integration. Good balance between speed and accuracy for most cases")},
{int(IntegrationScheme::RungeKutta3),
"RK3",
0,
N_("Runge-Kutta 3"),
N_("3rd order Runge-Kutta integration. Higher accuracy at moderate computational cost")},
{int(IntegrationScheme::RungeKutta4),
"RK4",
0,
N_("Runge-Kutta 4"),
N_("4th order Runge-Kutta integration. Highest accuracy single-step method but slower")},
{int(IntegrationScheme::MacCormack),
"MAC",
0,
N_("MacCormack"),
N_("MacCormack scheme with implicit diffusion control. Reduces numerical dissipation while "
"maintaining stability")},
{int(IntegrationScheme::BFECC),
"BFECC",
0,
N_("BFECC"),
N_("Back and Forth Error Compensation and Correction. Advanced scheme that minimizes "
"dissipation and diffusion")},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem limiter_type_items[] = {
{int(LimiterType::None),
"NONE",
0,
N_("None"),
N_("No limiting applied. Fastest but may produce artifacts in high-order schemes")},
{int(LimiterType::Clamp),
"CLAMP",
0,
N_("Clamp"),
N_("Clamp values to the range of the original neighborhood. Prevents overshooting and "
"undershooting")},
{int(LimiterType::Revert),
"REVERT",
0,
N_("Revert"),
N_("Revert to 1st order integration when clamping would be applied. More conservative than "
"clamping")},
{0, nullptr, 0, nullptr, nullptr},
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
const bNode *node = b.node_or_null();
if (!node) {
return;
}
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
b.add_input(data_type, "Grid"_ustr)
.hide_value()
.structure_type(StructureType::Grid)
.is_default_link_socket();
b.add_output(data_type, "Grid"_ustr).structure_type(StructureType::Grid).align_with_previous();
b.add_input<decl::Vector>("Velocity"_ustr).hide_value().structure_type(StructureType::Grid);
b.add_input<decl::Float>("Time Step"_ustr)
.subtype(PROP_TIME_ABSOLUTE)
.default_value(1.0f)
.description("Time step for advection in seconds");
b.add_input<decl::Menu>("Integration Scheme"_ustr)
.static_items(integration_scheme_items)
.default_value(IntegrationScheme::RungeKutta3)
.optional_label()
.description("Numerical integration method for advection");
b.add_input<decl::Menu>("Limiter"_ustr)
.static_items(limiter_type_items)
.default_value(LimiterType::Clamp)
.optional_label()
.description("Limiting strategy to prevent numerical artifacts");
}
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
{
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
}
static std::optional<eNodeSocketDatatype> node_type_for_socket_type(const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT:
return SOCK_FLOAT;
case SOCK_INT:
return SOCK_INT;
case SOCK_VECTOR:
case SOCK_RGBA:
return SOCK_VECTOR;
default:
return std::nullopt;
}
}
static void node_gather_link_search_ops(GatherLinkSearchOpParams &params)
{
const std::optional<eNodeSocketDatatype> data_type = node_type_for_socket_type(
params.other_socket());
if (!data_type) {
return;
}
if (params.in_out() == SOCK_IN) {
if (params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_VECTOR)) {
params.add_item(IFACE_("Velocity"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeGridAdvect"_ustr);
params.update_and_connect_available_socket(node, "Velocity"_ustr);
});
}
if (params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_FLOAT)) {
params.add_item(IFACE_("Time Step"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeGridAdvect"_ustr);
params.update_and_connect_available_socket(node, "Time Step"_ustr);
});
}
}
params.add_item(IFACE_("Grid"), [data_type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeGridAdvect"_ustr);
node.custom1 = *data_type;
params.update_and_connect_available_socket(node, "Grid"_ustr);
});
}
#ifdef WITH_OPENVDB
static openvdb::tools::Scheme::SemiLagrangian to_openvdb_scheme(const IntegrationScheme scheme)
{
switch (scheme) {
case IntegrationScheme::SemiLagrangian:
return openvdb::tools::Scheme::SEMI;
case IntegrationScheme::Midpoint:
return openvdb::tools::Scheme::MID;
case IntegrationScheme::RungeKutta3:
return openvdb::tools::Scheme::RK3;
case IntegrationScheme::RungeKutta4:
return openvdb::tools::Scheme::RK4;
case IntegrationScheme::MacCormack:
return openvdb::tools::Scheme::MAC;
case IntegrationScheme::BFECC:
return openvdb::tools::Scheme::BFECC;
}
return openvdb::tools::Scheme::SEMI;
}
static openvdb::tools::Scheme::Limiter to_openvdb_limiter(const LimiterType limiter)
{
switch (limiter) {
case LimiterType::None:
return openvdb::tools::Scheme::NO_LIMITER;
case LimiterType::Clamp:
return openvdb::tools::Scheme::CLAMP;
case LimiterType::Revert:
return openvdb::tools::Scheme::REVERT;
}
return openvdb::tools::Scheme::NO_LIMITER;
}
template<typename GridType, typename SamplerType = openvdb::tools::Sampler<1>>
static typename GridType::Ptr advect_grid(const GridType &grid,
const openvdb::Vec3SGrid &velocity_grid,
const float time_step,
const IntegrationScheme scheme,
const LimiterType limiter)
{
openvdb::tools::VolumeAdvection<openvdb::Vec3SGrid, false> advection(velocity_grid);
advection.setIntegrator(to_openvdb_scheme(scheme));
advection.setLimiter(to_openvdb_limiter(limiter));
return advection.template advect<GridType, SamplerType>(grid, time_step);
}
#endif
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
bke::GVolumeGrid grid = params.extract_input<bke::GVolumeGrid>("Grid"_ustr);
if (!grid) {
params.set_default_remaining_outputs();
return;
}
const bke::VolumeGrid<float3> velocity_grid = params.extract_input<bke::VolumeGrid<float3>>(
"Velocity"_ustr);
if (!velocity_grid) {
params.set_output("Grid"_ustr, std::move(grid));
return;
}
const float time_step = params.extract_input<float>("Time Step"_ustr);
const IntegrationScheme scheme = params.extract_input<IntegrationScheme>(
"Integration Scheme"_ustr);
const LimiterType limiter = params.extract_input<LimiterType>("Limiter"_ustr);
bke::VolumeTreeAccessToken tree_token;
bke::VolumeTreeAccessToken velocity_token;
const openvdb::GridBase &grid_base = grid->grid(tree_token);
const openvdb::Vec3SGrid &velocity_vdb_grid = velocity_grid.grid(velocity_token);
/* OpenVDB's advection requires uniform voxel scale on the grid being advected
* but not for the velocity grid being sampled. */
if (!grid_base.hasUniformVoxels()) {
params.error_message_add(
NodeWarningType::Error,
TIP_("The input grid must have a uniform voxel scale to be advected."));
params.set_output("Grid"_ustr, std::move(grid));
return;
}
const VolumeGridType grid_type = grid->grid_type();
BKE_volume_grid_type_to_static_type(
grid_type, [&]<std::derived_from<openvdb::GridBase> GridType>() {
if constexpr (std::is_same_v<GridType, openvdb::FloatGrid> ||
std::is_same_v<GridType, openvdb::Int32Grid> ||
std::is_same_v<GridType, openvdb::Vec3fGrid>)
{
typename GridType::Ptr result = advect_grid(
static_cast<const GridType &>(grid->grid(tree_token)),
velocity_vdb_grid,
time_step,
scheme,
limiter);
params.set_output("Grid"_ustr, bke::GVolumeGrid(std::move(result)));
}
else {
params.error_message_add(NodeWarningType::Error, "Unsupported grid type for advection");
params.set_default_remaining_outputs();
}
});
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = SOCK_FLOAT;
}
/* Only float, int, and vector grids are supported for advection */
static const EnumPropertyItem *advect_grid_socket_type_filter(bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free)
{
*r_free = true;
return enum_items_filter(rna_enum_node_socket_data_type_items,
[](const EnumPropertyItem &item) -> bool {
const eNodeSocketDatatype type = eNodeSocketDatatype(item.value);
return ELEM(type, SOCK_FLOAT, SOCK_INT, SOCK_VECTOR);
});
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"Node socket data type",
rna_enum_node_socket_data_type_items,
NOD_inline_enum_accessors(custom1),
SOCK_FLOAT,
advect_grid_socket_type_filter);
}
static const bNodeSocket *node_internally_linked_input(const bNodeTree & /*tree*/,
const bNode &node,
const bNodeSocket &output_socket)
{
return node.input_by_identifier(output_socket.identifier_ustr());
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGridAdvect"_ustr);
ntype.ui_name = "Advect Grid";
ntype.ui_description =
"Move grid values through a velocity field using numerical integration. Supports multiple "
"integration schemes for different accuracy and performance trade-offs";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.initfunc = node_init;
ntype.gather_link_search_ops = node_gather_link_search_ops;
ntype.internally_linked_input = node_internally_linked_input;
ntype.geometry_node_execute = node_geo_exec;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_grid_advect_cc

Some files were not shown because too many files have changed in this diff Show More