Geometry Nodes: SDF Grid filtering nodes
This PR gives access to OpenVDB's level set filtering operations through individual **SDF Grid** nodes. - **Mean Curvature**: Smoothens high curvature areas more than flatter ones. - **Laplacian**: Approximates mean curvature flow for true SDFs at lower computational cost. - **Median**: Reduces noise while preserving sharp features. - **Mean**: Fast separable averaging filter for general-purpose smoothing with linear computational complexity. - **Offset**: Uniform dilation/erosion operation that shifts the SDF surface by a world-space distance. - **Fillet**: Rounds off concave internal corners by operating only on regions with negative principal curvature. Pull Request: https://projects.blender.org/blender/blender/pulls/147224
This commit is contained in:
committed by
Hans Goudey
parent
d33a6a1723
commit
438b8c303e
@@ -950,6 +950,12 @@ class NODE_MT_gn_volume_operations_base(node_add_menu.NodeMenu):
|
||||
self.node_operator(layout, "GeometryNodeVolumeToMesh")
|
||||
self.node_operator(layout, "GeometryNodeGridToMesh")
|
||||
self.node_operator(layout, "GeometryNodeSDFGridBoolean")
|
||||
self.node_operator(layout, "GeometryNodeSDFGridFillet")
|
||||
self.node_operator(layout, "GeometryNodeSDFGridLaplacian")
|
||||
self.node_operator(layout, "GeometryNodeSDFGridMean")
|
||||
self.node_operator(layout, "GeometryNodeSDFGridMeanCurvature")
|
||||
self.node_operator(layout, "GeometryNodeSDFGridMedian")
|
||||
self.node_operator(layout, "GeometryNodeSDFGridOffset")
|
||||
self.node_operator(layout, "GeometryNodeFieldToGrid")
|
||||
self.node_operator(layout, "GeometryNodeGridPrune")
|
||||
self.node_operator(layout, "GeometryNodeGridVoxelize")
|
||||
|
||||
@@ -10276,6 +10276,12 @@ static void rna_def_nodes(BlenderRNA *brna)
|
||||
define("GeometryNode", "GeometryNodeScaleElements");
|
||||
define("GeometryNode", "GeometryNodeScaleInstances");
|
||||
define("GeometryNode", "GeometryNodeSDFGridBoolean");
|
||||
define("GeometryNode", "GeometryNodeSDFGridFillet");
|
||||
define("GeometryNode", "GeometryNodeSDFGridLaplacian");
|
||||
define("GeometryNode", "GeometryNodeSDFGridMean");
|
||||
define("GeometryNode", "GeometryNodeSDFGridMeanCurvature");
|
||||
define("GeometryNode", "GeometryNodeSDFGridMedian");
|
||||
define("GeometryNode", "GeometryNodeSDFGridOffset");
|
||||
define("GeometryNode", "GeometryNodeSelfObject");
|
||||
define("GeometryNode", "GeometryNodeSeparateComponents");
|
||||
define("GeometryNode", "GeometryNodeSeparateGeometry");
|
||||
|
||||
@@ -210,6 +210,12 @@ set(SRC
|
||||
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
|
||||
|
||||
@@ -40,6 +40,14 @@ void search_link_ops_for_tool_node(GatherLinkSearchOpParams ¶ms)
|
||||
}
|
||||
}
|
||||
|
||||
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*/,
|
||||
|
||||
@@ -47,6 +47,8 @@ namespace blender::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,
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_volume_grid.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
# include "openvdb/tools/LevelSetFilter.h"
|
||||
#endif
|
||||
|
||||
namespace blender::nodes::node_geo_sdf_grid_fillet_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Float>("Grid").hide_value().structure_type(StructureType::Grid);
|
||||
b.add_output<decl::Float>("Grid").structure_type(StructureType::Grid).align_with_previous();
|
||||
b.add_input<decl::Int>("Iterations")
|
||||
.default_value(1)
|
||||
.min(0)
|
||||
.description("Number of iterations to apply the filter");
|
||||
}
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
auto grid = params.extract_input<bke::VolumeGrid<float>>("Grid");
|
||||
if (!grid) {
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
const int iterations = params.extract_input<int>("Iterations");
|
||||
if (iterations <= 0) {
|
||||
params.set_output("Grid", std::move(grid));
|
||||
return;
|
||||
}
|
||||
|
||||
bke::VolumeTreeAccessToken tree_token;
|
||||
openvdb::FloatGrid &vdb_grid = grid.grid_for_write(tree_token);
|
||||
|
||||
try {
|
||||
openvdb::tools::LevelSetFilter<openvdb::FloatGrid> filter(vdb_grid);
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
filter.fillet();
|
||||
}
|
||||
}
|
||||
catch (const openvdb::RuntimeError &e) {
|
||||
node_geo_sdf_grid_error_not_levelset(params);
|
||||
return;
|
||||
}
|
||||
|
||||
params.set_output("Grid", std::move(grid));
|
||||
#else
|
||||
node_geo_exec_with_missing_openvdb(params);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static blender::bke::bNodeType ntype;
|
||||
geo_node_type_base(&ntype, "GeometryNodeSDFGridFillet");
|
||||
ntype.ui_name = "SDF Grid Fillet";
|
||||
ntype.ui_description =
|
||||
"Round off concave internal corners in a signed distance field. Only affects areas with "
|
||||
"negative principal curvature, creating smoother transitions between surfaces";
|
||||
ntype.nclass = NODE_CLASS_GEOMETRY;
|
||||
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_sdf_grid_fillet_cc
|
||||
@@ -0,0 +1,77 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_volume_grid.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
# include "openvdb/tools/LevelSetFilter.h"
|
||||
#endif
|
||||
|
||||
namespace blender::nodes::node_geo_sdf_grid_laplacian_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Float>("Grid").hide_value().structure_type(StructureType::Grid);
|
||||
b.add_output<decl::Float>("Grid").structure_type(StructureType::Grid).align_with_previous();
|
||||
b.add_input<decl::Int>("Iterations")
|
||||
.default_value(1)
|
||||
.min(0)
|
||||
.description("Number of iterations to apply the filter");
|
||||
}
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
auto grid = params.extract_input<bke::VolumeGrid<float>>("Grid");
|
||||
if (!grid) {
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
const int iterations = params.extract_input<int>("Iterations");
|
||||
if (iterations <= 0) {
|
||||
params.set_output("Grid", std::move(grid));
|
||||
return;
|
||||
}
|
||||
|
||||
bke::VolumeTreeAccessToken tree_token;
|
||||
openvdb::FloatGrid &vdb_grid = grid.grid_for_write(tree_token);
|
||||
|
||||
try {
|
||||
openvdb::tools::LevelSetFilter<openvdb::FloatGrid> filter(vdb_grid);
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
filter.laplacian();
|
||||
}
|
||||
}
|
||||
catch (const openvdb::RuntimeError &e) {
|
||||
node_geo_sdf_grid_error_not_levelset(params);
|
||||
return;
|
||||
}
|
||||
|
||||
params.set_output("Grid", std::move(grid));
|
||||
#else
|
||||
node_geo_exec_with_missing_openvdb(params);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static blender::bke::bNodeType ntype;
|
||||
geo_node_type_base(&ntype, "GeometryNodeSDFGridLaplacian");
|
||||
ntype.ui_name = "SDF Grid Laplacian";
|
||||
ntype.ui_description =
|
||||
"Apply Laplacian flow smoothing to a signed distance field. Computationally efficient "
|
||||
"alternative to mean curvature flow, ideal when combined with SDF normalization";
|
||||
ntype.nclass = NODE_CLASS_GEOMETRY;
|
||||
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_sdf_grid_laplacian_cc
|
||||
@@ -0,0 +1,80 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_volume_grid.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
# include "openvdb/tools/LevelSetFilter.h"
|
||||
#endif
|
||||
|
||||
namespace blender::nodes::node_geo_sdf_grid_mean_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Float>("Grid").hide_value().structure_type(StructureType::Grid);
|
||||
b.add_output<decl::Float>("Grid").structure_type(StructureType::Grid).align_with_previous();
|
||||
b.add_input<decl::Int>("Width").default_value(1).min(0).description(
|
||||
"Filter kernel radius in voxels");
|
||||
b.add_input<decl::Int>("Iterations")
|
||||
.default_value(1)
|
||||
.min(0)
|
||||
.description("Number of iterations to apply the filter");
|
||||
}
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
auto grid = params.extract_input<bke::VolumeGrid<float>>("Grid");
|
||||
if (!grid) {
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
const int iterations = params.extract_input<int>("Iterations");
|
||||
const int width = params.extract_input<int>("Width");
|
||||
if (iterations <= 0 || width <= 0) {
|
||||
params.set_output("Grid", std::move(grid));
|
||||
return;
|
||||
}
|
||||
|
||||
bke::VolumeTreeAccessToken tree_token;
|
||||
openvdb::FloatGrid &vdb_grid = grid.grid_for_write(tree_token);
|
||||
|
||||
try {
|
||||
openvdb::tools::LevelSetFilter<openvdb::FloatGrid> filter(vdb_grid);
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
filter.mean(width);
|
||||
}
|
||||
}
|
||||
catch (const openvdb::RuntimeError &e) {
|
||||
node_geo_sdf_grid_error_not_levelset(params);
|
||||
return;
|
||||
}
|
||||
|
||||
params.set_output("Grid", std::move(grid));
|
||||
#else
|
||||
node_geo_exec_with_missing_openvdb(params);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static blender::bke::bNodeType ntype;
|
||||
geo_node_type_base(&ntype, "GeometryNodeSDFGridMean");
|
||||
ntype.ui_name = "SDF Grid Mean";
|
||||
ntype.ui_description =
|
||||
"Apply mean (box) filter smoothing to a signed distance field. Fast separable averaging "
|
||||
"filter for general smoothing of the distance field";
|
||||
ntype.nclass = NODE_CLASS_GEOMETRY;
|
||||
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_sdf_grid_mean_cc
|
||||
@@ -0,0 +1,77 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_volume_grid.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
# include "openvdb/tools/LevelSetFilter.h"
|
||||
#endif
|
||||
|
||||
namespace blender::nodes::node_geo_sdf_grid_mean_curvature_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Float>("Grid").hide_value().structure_type(StructureType::Grid);
|
||||
b.add_output<decl::Float>("Grid").structure_type(StructureType::Grid).align_with_previous();
|
||||
b.add_input<decl::Int>("Iterations")
|
||||
.default_value(1)
|
||||
.min(0)
|
||||
.description("Number of iterations to apply the filter");
|
||||
}
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
auto grid = params.extract_input<bke::VolumeGrid<float>>("Grid");
|
||||
if (!grid) {
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
const int iterations = params.extract_input<int>("Iterations");
|
||||
if (iterations <= 0) {
|
||||
params.set_output("Grid", std::move(grid));
|
||||
return;
|
||||
}
|
||||
|
||||
bke::VolumeTreeAccessToken tree_token;
|
||||
openvdb::FloatGrid &vdb_grid = grid.grid_for_write(tree_token);
|
||||
|
||||
try {
|
||||
openvdb::tools::LevelSetFilter<openvdb::FloatGrid> filter(vdb_grid);
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
filter.meanCurvature();
|
||||
}
|
||||
}
|
||||
catch (const openvdb::RuntimeError &e) {
|
||||
node_geo_sdf_grid_error_not_levelset(params);
|
||||
return;
|
||||
}
|
||||
|
||||
params.set_output("Grid", std::move(grid));
|
||||
#else
|
||||
node_geo_exec_with_missing_openvdb(params);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static blender::bke::bNodeType ntype;
|
||||
geo_node_type_base(&ntype, "GeometryNodeSDFGridMeanCurvature");
|
||||
ntype.ui_name = "SDF Grid Mean Curvature";
|
||||
ntype.ui_description =
|
||||
"Apply mean curvature flow smoothing to a signed distance field. Evolves the surface based "
|
||||
"on its mean curvature, naturally smoothing high-curvature regions more than flat areas";
|
||||
ntype.nclass = NODE_CLASS_GEOMETRY;
|
||||
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_sdf_grid_mean_curvature_cc
|
||||
@@ -0,0 +1,80 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_volume_grid.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
# include "openvdb/tools/LevelSetFilter.h"
|
||||
#endif
|
||||
|
||||
namespace blender::nodes::node_geo_sdf_grid_median_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Float>("Grid").hide_value().structure_type(StructureType::Grid);
|
||||
b.add_output<decl::Float>("Grid").structure_type(StructureType::Grid).align_with_previous();
|
||||
b.add_input<decl::Int>("Width").default_value(1).min(0).description(
|
||||
"Filter kernel radius in voxels");
|
||||
b.add_input<decl::Int>("Iterations")
|
||||
.default_value(1)
|
||||
.min(0)
|
||||
.description("Number of iterations to apply the filter");
|
||||
}
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
auto grid = params.extract_input<bke::VolumeGrid<float>>("Grid");
|
||||
if (!grid) {
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
const int iterations = params.extract_input<int>("Iterations");
|
||||
const int width = params.extract_input<int>("Width");
|
||||
if (iterations <= 0 || width <= 0) {
|
||||
params.set_output("Grid", std::move(grid));
|
||||
return;
|
||||
}
|
||||
|
||||
bke::VolumeTreeAccessToken tree_token;
|
||||
openvdb::FloatGrid &vdb_grid = grid.grid_for_write(tree_token);
|
||||
|
||||
try {
|
||||
openvdb::tools::LevelSetFilter<openvdb::FloatGrid> filter(vdb_grid);
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
filter.median(width);
|
||||
}
|
||||
}
|
||||
catch (const openvdb::RuntimeError &e) {
|
||||
node_geo_sdf_grid_error_not_levelset(params);
|
||||
return;
|
||||
}
|
||||
|
||||
params.set_output("Grid", std::move(grid));
|
||||
#else
|
||||
node_geo_exec_with_missing_openvdb(params);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static blender::bke::bNodeType ntype;
|
||||
geo_node_type_base(&ntype, "GeometryNodeSDFGridMedian");
|
||||
ntype.ui_name = "SDF Grid Median";
|
||||
ntype.ui_description =
|
||||
"Apply median filter to a signed distance field. Reduces noise while preserving sharp "
|
||||
"features and edges in the distance field";
|
||||
ntype.nclass = NODE_CLASS_GEOMETRY;
|
||||
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_sdf_grid_median_cc
|
||||
@@ -0,0 +1,71 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_volume_grid.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
# include "openvdb/tools/LevelSetFilter.h"
|
||||
#endif
|
||||
|
||||
namespace blender::nodes::node_geo_sdf_grid_offset_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Float>("Grid").hide_value().structure_type(StructureType::Grid);
|
||||
b.add_output<decl::Float>("Grid").structure_type(StructureType::Grid).align_with_previous();
|
||||
b.add_input<decl::Float>("Distance")
|
||||
.subtype(PROP_DISTANCE)
|
||||
.default_value(0.1f)
|
||||
.description("Object-space distance to offset the SDF surface");
|
||||
}
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
auto grid = params.extract_input<bke::VolumeGrid<float>>("Grid");
|
||||
if (!grid) {
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
const float distance = params.extract_input<float>("Distance");
|
||||
|
||||
bke::VolumeTreeAccessToken tree_token;
|
||||
openvdb::FloatGrid &vdb_grid = grid.grid_for_write(tree_token);
|
||||
|
||||
try {
|
||||
openvdb::tools::LevelSetFilter<openvdb::FloatGrid> filter(vdb_grid);
|
||||
filter.offset(-distance);
|
||||
}
|
||||
catch (const openvdb::RuntimeError &e) {
|
||||
node_geo_sdf_grid_error_not_levelset(params);
|
||||
return;
|
||||
}
|
||||
|
||||
params.set_output("Grid", std::move(grid));
|
||||
#else
|
||||
node_geo_exec_with_missing_openvdb(params);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static blender::bke::bNodeType ntype;
|
||||
geo_node_type_base(&ntype, "GeometryNodeSDFGridOffset");
|
||||
ntype.ui_name = "SDF Grid Offset";
|
||||
ntype.ui_description =
|
||||
"Offset a signed distance field surface by a world-space distance. Dilates (positive) or "
|
||||
"erodes (negative) while maintaining the signed distance property";
|
||||
ntype.nclass = NODE_CLASS_GEOMETRY;
|
||||
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_sdf_grid_offset_cc
|
||||
BIN
tests/files/modeling/geometry_nodes/volume/sdf_grid_filtering.blend
(Stored with Git LFS)
Normal file
BIN
tests/files/modeling/geometry_nodes/volume/sdf_grid_filtering.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
Reference in New Issue
Block a user