Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/startup/nodeitems_builtins.py12
-rw-r--r--source/blender/blenkernel/BKE_node.h2
-rw-r--r--source/blender/blenkernel/intern/node.cc2
-rw-r--r--source/blender/makesdna/DNA_node_types.h10
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c27
-rw-r--r--source/blender/nodes/NOD_geometry.h2
-rw-r--r--source/blender/nodes/NOD_static_types.h2
-rw-r--r--source/blender/nodes/geometry/CMakeLists.txt2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc152
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc199
10 files changed, 410 insertions, 0 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index 5983596dab1..21bb3d01616 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -157,6 +157,17 @@ def geometry_node_items(context):
yield NodeItem("GeometryNodeSetPosition")
+# Custom Menu for UV Nodes.
+def uv_node_items(context):
+ if context is None:
+ return
+ space = context.space_data
+ if not space:
+ return
+ yield NodeItem("GeometryNodeUVUnwrap")
+ yield NodeItem("GeometryNodeUVPackIslands")
+
+
# Custom Menu for Geometry Node Input Nodes.
def geometry_input_node_items(context):
if context is None:
@@ -654,6 +665,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeCurvePrimitiveBezierSegment"),
]),
GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=geometry_node_items),
+ GeometryNodeCategory("GEO_UV", "UV", items=uv_node_items),
GeometryNodeCategory("GEO_INPUT", "Input", items=geometry_input_node_items),
GeometryNodeCategory("GEO_INSTANCE", "Instances", items=geometry_instance_node_items),
GeometryNodeCategory("GEO_MATERIAL", "Material", items=geometry_material_node_items),
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index bad3e0005b8..e13ac3180ec 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1499,6 +1499,8 @@ struct TexResult;
#define GEO_NODE_POINTS 1162
#define GEO_NODE_FIELD_ON_DOMAIN 1163
#define GEO_NODE_MESH_TO_VOLUME 1164
+#define GEO_NODE_UV_UNWRAP 1165
+#define GEO_NODE_UV_PACK_ISLANDS 1166
/** \} */
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 244c9ccd048..5be912ffb2b 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4841,6 +4841,8 @@ static void registerGeometryNodes()
register_node_type_geo_viewer();
register_node_type_geo_volume_cube();
register_node_type_geo_volume_to_mesh();
+ register_node_type_geo_uv_pack_islands();
+ register_node_type_geo_uv_unwrap();
}
static void registerFunctionNodes()
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index 3f3eb6e0571..76d8207eead 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -1455,6 +1455,11 @@ typedef struct NodeGeometryViewer {
int8_t data_type;
} NodeGeometryViewer;
+typedef struct NodeGeometryUVUnwrap {
+ /* GeometryNodeUVUnwrapMethod. */
+ uint8_t method;
+} NodeGeometryUVUnwrap;
+
typedef struct NodeFunctionCompare {
/* NodeCompareOperation */
int8_t operation;
@@ -2005,6 +2010,11 @@ typedef enum GeometryNodeMergeByDistanceMode {
GEO_NODE_MERGE_BY_DISTANCE_MODE_CONNECTED = 1,
} GeometryNodeMergeByDistanceMode;
+typedef enum GeometryNodeUVUnwrapMethod {
+ GEO_NODE_UV_UNWRAP_METHOD_ANGLE_BASED = 0,
+ GEO_NODE_UV_UNWRAP_METHOD_CONFORMAL = 1,
+} GeometryNodeUVUnwrapMethod;
+
typedef enum GeometryNodeMeshLineMode {
GEO_NODE_MESH_LINE_MODE_END_POINTS = 0,
GEO_NODE_MESH_LINE_MODE_OFFSET = 1,
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index 0ad109e711f..386ef3f74a3 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -9879,6 +9879,33 @@ static void def_geo_points_to_volume(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
+static void def_geo_uv_unwrap(StructRNA *srna)
+{
+ PropertyRNA *prop;
+
+ static EnumPropertyItem rna_node_geometry_uv_unwrap_method_items[] = {
+ {GEO_NODE_UV_UNWRAP_METHOD_ANGLE_BASED,
+ "ANGLE_BASED",
+ 0,
+ "Angle Based",
+ "This method gives a good 2D representation of a mesh"},
+ {GEO_NODE_UV_UNWRAP_METHOD_CONFORMAL,
+ "CONFORMAL",
+ 0,
+ "Conformal",
+ "Uses LSCM (Least Squares Conformal Mapping). This usually gives a less accurate UV "
+ "mapping than Angle Based, but works better for simpler objects"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ RNA_def_struct_sdna_from(srna, "NodeGeometryUVUnwrap", "storage");
+
+ prop = RNA_def_property(srna, "method", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, rna_node_geometry_uv_unwrap_method_items);
+ RNA_def_property_ui_text(prop, "Method", "");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+}
+
static void def_geo_collection_info(StructRNA *srna)
{
PropertyRNA *prop;
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 889ab2a81e4..8f15add33fd 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -140,6 +140,8 @@ void register_node_type_geo_triangulate(void);
void register_node_type_geo_viewer(void);
void register_node_type_geo_volume_cube(void);
void register_node_type_geo_volume_to_mesh(void);
+void register_node_type_geo_uv_pack_islands(void);
+void register_node_type_geo_uv_unwrap(void);
#ifdef __cplusplus
}
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index f9925924260..609791ad091 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -402,6 +402,8 @@ DefNode(GeometryNode, GEO_NODE_TRIM_CURVE, def_geo_curve_trim, "TRIM_CURVE", Tri
DefNode(GeometryNode, GEO_NODE_VIEWER, def_geo_viewer, "VIEWER", Viewer, "Viewer", "")
DefNode(GeometryNode, GEO_NODE_VOLUME_CUBE, 0, "VOLUME_CUBE", VolumeCube, "Volume Cube", "")
DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "")
+DefNode(GeometryNode, GEO_NODE_UV_PACK_ISLANDS, 0, "UV_PACK_ISLANDS", UVPackIslands, "Pack UV Islands", "")
+DefNode(GeometryNode, GEO_NODE_UV_UNWRAP, def_geo_uv_unwrap, "UV_UNWRAP", UVUnwrap, "UV Unwrap", "")
/* undefine macros */
#undef DefNode
diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt
index 9ef3151ddab..950124f75d0 100644
--- a/source/blender/nodes/geometry/CMakeLists.txt
+++ b/source/blender/nodes/geometry/CMakeLists.txt
@@ -146,6 +146,8 @@ set(SRC
nodes/node_geo_transform.cc
nodes/node_geo_translate_instances.cc
nodes/node_geo_triangulate.cc
+ nodes/node_geo_uv_pack_islands.cc
+ nodes/node_geo_uv_unwrap.cc
nodes/node_geo_viewer.cc
nodes/node_geo_volume_cube.cc
nodes/node_geo_volume_to_mesh.cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc b/source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc
new file mode 100644
index 00000000000..feff6efc3f8
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "GEO_uv_parametrizer.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_uv_pack_islands_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Vector>(N_("UV")).hide_value().supports_field();
+ b.add_input<decl::Bool>(N_("Selection"))
+ .default_value(true)
+ .hide_value()
+ .supports_field()
+ .description(N_("Faces to consider when packing islands"));
+ b.add_input<decl::Float>(N_("Margin"))
+ .default_value(0.001f)
+ .min(0.0f)
+ .max(1.0f)
+ .description(N_("Space between islands"));
+ b.add_input<decl::Bool>(N_("Rotate"))
+ .default_value(true)
+ .description(N_("Rotate islands for best fit"));
+ b.add_output<decl::Vector>(N_("UV")).field_source();
+}
+
+static VArray<float3> construct_uv_gvarray(const MeshComponent &component,
+ const Field<bool> selection_field,
+ const Field<float3> uv_field,
+ const bool rotate,
+ const float margin,
+ const eAttrDomain domain)
+{
+ const Mesh *mesh = component.get_for_read();
+ if (mesh == nullptr) {
+ return {};
+ }
+
+ const int face_num = component.attribute_domain_num(ATTR_DOMAIN_FACE);
+ GeometryComponentFieldContext face_context{component, ATTR_DOMAIN_FACE};
+ FieldEvaluator face_evaluator{face_context, face_num};
+ face_evaluator.add(selection_field);
+ face_evaluator.evaluate();
+ const IndexMask selection = face_evaluator.get_evaluated_as_mask(0);
+ if (selection.is_empty()) {
+ return {};
+ }
+
+ const int corner_num = component.attribute_domain_num(ATTR_DOMAIN_CORNER);
+ GeometryComponentFieldContext corner_context{component, ATTR_DOMAIN_CORNER};
+ FieldEvaluator evaluator{corner_context, corner_num};
+ Array<float3> uv(corner_num);
+ evaluator.add_with_destination(uv_field, uv.as_mutable_span());
+ evaluator.evaluate();
+
+ ParamHandle *handle = GEO_uv_parametrizer_construct_begin();
+ for (const int mp_index : selection) {
+ const MPoly &mp = mesh->mpoly[mp_index];
+ Array<ParamKey, 16> mp_vkeys(mp.totloop);
+ Array<bool, 16> mp_pin(mp.totloop);
+ Array<bool, 16> mp_select(mp.totloop);
+ Array<const float *, 16> mp_co(mp.totloop);
+ Array<float *, 16> mp_uv(mp.totloop);
+ for (const int i : IndexRange(mp.totloop)) {
+ const MLoop &ml = mesh->mloop[mp.loopstart + i];
+ mp_vkeys[i] = ml.v;
+ mp_co[i] = mesh->mvert[ml.v].co;
+ mp_uv[i] = uv[mp.loopstart + i];
+ mp_pin[i] = false;
+ mp_select[i] = false;
+ }
+ GEO_uv_parametrizer_face_add(handle,
+ mp_index,
+ mp.totloop,
+ mp_vkeys.data(),
+ mp_co.data(),
+ mp_uv.data(),
+ mp_pin.data(),
+ mp_select.data());
+ }
+ GEO_uv_parametrizer_construct_end(handle, true, true, nullptr);
+
+ GEO_uv_parametrizer_pack(handle, margin, rotate, true);
+ GEO_uv_parametrizer_flush(handle);
+ GEO_uv_parametrizer_delete(handle);
+
+ return component.attribute_try_adapt_domain<float3>(
+ VArray<float3>::ForContainer(std::move(uv)), ATTR_DOMAIN_CORNER, domain);
+}
+
+class PackIslandsFieldInput final : public GeometryFieldInput {
+ private:
+ const Field<bool> selection_field;
+ const Field<float3> uv_field;
+ const bool rotate;
+ const float margin;
+
+ public:
+ PackIslandsFieldInput(const Field<bool> selection_field,
+ const Field<float3> uv_field,
+ const bool rotate,
+ const float margin)
+ : GeometryFieldInput(CPPType::get<float3>(), "Pack UV Islands Field"),
+ selection_field(selection_field),
+ uv_field(uv_field),
+ rotate(rotate),
+ margin(margin)
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const GeometryComponent &component,
+ const eAttrDomain domain,
+ IndexMask UNUSED(mask)) const final
+ {
+ if (component.type() == GEO_COMPONENT_TYPE_MESH) {
+ const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component);
+ return construct_uv_gvarray(
+ mesh_component, selection_field, uv_field, rotate, margin, domain);
+ }
+ return {};
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
+ const Field<float3> uv_field = params.extract_input<Field<float3>>("UV");
+ const bool rotate = params.extract_input<bool>("Rotate");
+ const float margin = params.extract_input<float>("Margin");
+ params.set_output("UV",
+ Field<float3>(std::make_shared<PackIslandsFieldInput>(
+ selection_field, uv_field, rotate, margin)));
+}
+
+} // namespace blender::nodes::node_geo_uv_pack_islands_cc
+
+void register_node_type_geo_uv_pack_islands()
+{
+ namespace file_ns = blender::nodes::node_geo_uv_pack_islands_cc;
+
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_UV_PACK_ISLANDS, "Pack UV Islands", NODE_CLASS_CONVERTER);
+ ntype.declare = file_ns::node_declare;
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc b/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc
new file mode 100644
index 00000000000..bf960c5c809
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc
@@ -0,0 +1,199 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "GEO_uv_parametrizer.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_uv_unwrap_cc {
+
+NODE_STORAGE_FUNCS(NodeGeometryUVUnwrap)
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Bool>(N_("Selection"))
+ .default_value(true)
+ .hide_value()
+ .supports_field()
+ .description(N_("Faces to participate in the unwrap operation"));
+ b.add_input<decl::Bool>(N_("Seam"))
+ .hide_value()
+ .supports_field()
+ .description(N_("Edges to mark where the mesh is \"cut\" for the purposes of unwrapping"));
+ b.add_input<decl::Float>(N_("Margin"))
+ .default_value(0.001f)
+ .min(0.0f)
+ .max(1.0f)
+ .description(N_("Space between islands"));
+ b.add_input<decl::Bool>(N_("Fill Holes"))
+ .default_value(true)
+ .description(N_("Virtually fill holes in mesh before unwrapping, to better avoid overlaps "
+ "and preserve symmetry"));
+ b.add_output<decl::Vector>(N_("UV")).field_source().description(
+ N_("UV coordinates between 0 and 1 for each face corner in the selected faces"));
+}
+
+static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetPropDecorate(layout, false);
+ uiItemR(layout, ptr, "method", 0, "", ICON_NONE);
+}
+
+static void node_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+ NodeGeometryUVUnwrap *data = MEM_cnew<NodeGeometryUVUnwrap>(__func__);
+ data->method = GEO_NODE_UV_UNWRAP_METHOD_ANGLE_BASED;
+ node->storage = data;
+}
+
+static VArray<float3> construct_uv_gvarray(const MeshComponent &component,
+ const Field<bool> selection_field,
+ const Field<bool> seam_field,
+ const bool fill_holes,
+ const float margin,
+ const GeometryNodeUVUnwrapMethod method,
+ const eAttrDomain domain)
+{
+ const Mesh *mesh = component.get_for_read();
+ if (mesh == nullptr) {
+ return {};
+ }
+
+ const int face_num = component.attribute_domain_num(ATTR_DOMAIN_FACE);
+ GeometryComponentFieldContext face_context{component, ATTR_DOMAIN_FACE};
+ FieldEvaluator face_evaluator{face_context, face_num};
+ face_evaluator.add(selection_field);
+ face_evaluator.evaluate();
+ const IndexMask selection = face_evaluator.get_evaluated_as_mask(0);
+ if (selection.is_empty()) {
+ return {};
+ }
+
+ const int edge_num = component.attribute_domain_num(ATTR_DOMAIN_EDGE);
+ GeometryComponentFieldContext edge_context{component, ATTR_DOMAIN_EDGE};
+ FieldEvaluator edge_evaluator{edge_context, edge_num};
+ edge_evaluator.add(seam_field);
+ edge_evaluator.evaluate();
+ const IndexMask seam = edge_evaluator.get_evaluated_as_mask(0);
+
+ Array<float3> uv(mesh->totloop);
+
+ ParamHandle *handle = GEO_uv_parametrizer_construct_begin();
+ for (const int mp_index : selection) {
+ const MPoly &mp = mesh->mpoly[mp_index];
+ Array<ParamKey, 16> mp_vkeys(mp.totloop);
+ Array<bool, 16> mp_pin(mp.totloop);
+ Array<bool, 16> mp_select(mp.totloop);
+ Array<const float *, 16> mp_co(mp.totloop);
+ Array<float *, 16> mp_uv(mp.totloop);
+ for (const int i : IndexRange(mp.totloop)) {
+ const MLoop &ml = mesh->mloop[mp.loopstart + i];
+ mp_vkeys[i] = ml.v;
+ mp_co[i] = mesh->mvert[ml.v].co;
+ mp_uv[i] = uv[mp.loopstart + i];
+ mp_pin[i] = false;
+ mp_select[i] = false;
+ }
+ GEO_uv_parametrizer_face_add(handle,
+ mp_index,
+ mp.totloop,
+ mp_vkeys.data(),
+ mp_co.data(),
+ mp_uv.data(),
+ mp_pin.data(),
+ mp_select.data());
+ }
+ for (const int i : seam) {
+ const MEdge &edge = mesh->medge[i];
+ ParamKey vkeys[2]{edge.v1, edge.v2};
+ GEO_uv_parametrizer_edge_set_seam(handle, vkeys);
+ }
+ /* TODO: once field input nodes are able to emit warnings (T94039), emit a
+ * warning if we fail to solve an island. */
+ GEO_uv_parametrizer_construct_end(handle, fill_holes, false, nullptr);
+
+ GEO_uv_parametrizer_lscm_begin(handle, false, method == GEO_NODE_UV_UNWRAP_METHOD_ANGLE_BASED);
+ GEO_uv_parametrizer_lscm_solve(handle, nullptr, nullptr);
+ GEO_uv_parametrizer_lscm_end(handle);
+ GEO_uv_parametrizer_average(handle, true);
+ GEO_uv_parametrizer_pack(handle, margin, true, true);
+ GEO_uv_parametrizer_flush(handle);
+ GEO_uv_parametrizer_delete(handle);
+
+ return component.attribute_try_adapt_domain<float3>(
+ VArray<float3>::ForContainer(std::move(uv)), ATTR_DOMAIN_CORNER, domain);
+}
+
+class UnwrapFieldInput final : public GeometryFieldInput {
+ private:
+ const Field<bool> selection;
+ const Field<bool> seam;
+ const bool fill_holes;
+ const float margin;
+ const GeometryNodeUVUnwrapMethod method;
+
+ public:
+ UnwrapFieldInput(const Field<bool> selection,
+ const Field<bool> seam,
+ const bool fill_holes,
+ const float margin,
+ const GeometryNodeUVUnwrapMethod method)
+ : GeometryFieldInput(CPPType::get<float3>(), "UV Unwrap Field"),
+ selection(selection),
+ seam(seam),
+ fill_holes(fill_holes),
+ margin(margin),
+ method(method)
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const GeometryComponent &component,
+ const eAttrDomain domain,
+ IndexMask UNUSED(mask)) const final
+ {
+ if (component.type() == GEO_COMPONENT_TYPE_MESH) {
+ const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component);
+ return construct_uv_gvarray(
+ mesh_component, selection, seam, fill_holes, margin, method, domain);
+ }
+ return {};
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ const NodeGeometryUVUnwrap &storage = node_storage(params.node());
+ const GeometryNodeUVUnwrapMethod method = (GeometryNodeUVUnwrapMethod)storage.method;
+ const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
+ const Field<bool> seam_field = params.extract_input<Field<bool>>("Seam");
+ const bool fill_holes = params.extract_input<bool>("Fill Holes");
+ const float margin = params.extract_input<float>("Margin");
+ params.set_output("UV",
+ Field<float3>(std::make_shared<UnwrapFieldInput>(
+ selection_field, seam_field, fill_holes, margin, method)));
+}
+
+} // namespace blender::nodes::node_geo_uv_unwrap_cc
+
+void register_node_type_geo_uv_unwrap()
+{
+ namespace file_ns = blender::nodes::node_geo_uv_unwrap_cc;
+
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_UV_UNWRAP, "UV Unwrap", NODE_CLASS_CONVERTER);
+ node_type_init(&ntype, file_ns::node_init);
+ node_type_storage(
+ &ntype, "NodeGeometryUVUnwrap", node_free_standard_storage, node_copy_standard_storage);
+ ntype.declare = file_ns::node_declare;
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.draw_buttons = file_ns::node_layout;
+ nodeRegisterType(&ntype);
+}