Add Chromium-only Blender WebEngine parity work
This commit is contained in:
407
blender-5.2.0/source/blender/nodes/geometry/CMakeLists.txt
Normal file
407
blender-5.2.0/source/blender/nodes/geometry/CMakeLists.txt
Normal 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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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
|
||||
@@ -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 ¶ms);
|
||||
void search_link_ops_for_tool_node(GatherLinkSearchOpParams ¶ms);
|
||||
|
||||
void node_geo_sdf_grid_error_not_levelset(GeoNodeExecParams ¶ms);
|
||||
|
||||
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 ¶ms);
|
||||
|
||||
void node_geo_exec_with_too_old_openvdb(GeoNodeExecParams ¶ms);
|
||||
|
||||
void draw_data_blocks(const bContext *C, ui::Layout &layout, PointerRNA &bake_rna);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
const eNodeSocketDatatype type = params.other_socket().type;
|
||||
if (type == SOCK_GEOMETRY) {
|
||||
params.add_item(IFACE_("Geometry"), [](LinkSearchOpParams ¶ms) {
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms, 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 ¶ms) const
|
||||
{
|
||||
set_default_remaining_node_outputs(params, node_);
|
||||
}
|
||||
|
||||
void pass_through(lf::Params ¶ms,
|
||||
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 ¶ms,
|
||||
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 ¶ms,
|
||||
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 ¶ms,
|
||||
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 ¶ms,
|
||||
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 ¶ms)
|
||||
{
|
||||
BakeDrawContext ctx;
|
||||
if (!get_bake_draw_context(¶ms.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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
bNode &node = params.add_node("GeometryNodeBoneInfo"_ustr);
|
||||
params.update_and_connect_available_socket(node, "Pose"_ustr);
|
||||
});
|
||||
params.add_item(IFACE_("Local Pose"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("GeometryNodeBoneInfo"_ustr);
|
||||
params.update_and_connect_available_socket(node, "Local Pose"_ustr);
|
||||
});
|
||||
params.add_item(IFACE_("Transform Pose"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("GeometryNodeBoneInfo"_ustr);
|
||||
params.update_and_connect_available_socket(node, "Transform Pose"_ustr);
|
||||
});
|
||||
params.add_item(IFACE_("Rest Pose"), [](LinkSearchOpParams ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
if (params.C && params.link.fromnode == ¶ms.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 == ¶ms.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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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(¶ms.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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms)
|
||||
{
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
if (params.C && params.link.fromnode == ¶ms.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 == ¶ms.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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
bNode &node = params.add_node("NodeCombineBundle"_ustr);
|
||||
params.connect_available_socket(node, "Bundle"_ustr);
|
||||
|
||||
SpaceNode &snode = *CTX_wm_space_node(¶ms.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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¢er_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
|
||||
@@ -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
|
||||
@@ -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 ¢er = 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¶ms, 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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¶ms, 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 ¶ms)
|
||||
{
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 == ¶ms.ntree) {
|
||||
sync_sockets_evaluate_closure(*snode, params.node, nullptr, params.link.fromsock);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (params.link.tonode == ¶ms.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 ¶ms)
|
||||
{
|
||||
const bNodeSocket &other_socket = params.other_socket();
|
||||
if (other_socket.in_out == SOCK_IN) {
|
||||
params.add_item(IFACE_("Item"), [](LinkSearchOpParams ¶ms) {
|
||||
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 ¶ms) {
|
||||
bNode &node = params.add_node("NodeEvaluateClosure"_ustr);
|
||||
params.connect_available_socket(node, "Closure"_ustr);
|
||||
|
||||
SpaceNode &snode = *CTX_wm_space_node(¶ms.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 ¶ms) {
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms)
|
||||
{
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms)
|
||||
{
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms,
|
||||
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
|
||||
@@ -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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¶ms,
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
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
Reference in New Issue
Block a user