Geometry Nodes: UV Unwrap and Pack Islands Nodes
This commit adds new Unwrap and Pack Islands nodes, with equivalent functionality to the existing Unwrap and Pack Islands operators. The Unwrap node uses generic boolean attributes to determine seams instead of looking at the seam flags in the mesh geometry. Unlike the Unwrap operator, the Unwrap node doesn't perform aspect ratio correction, because this is trivial for the user to implement with a Vector Math node if it is desired. The Unwrap node implicitly performs a Pack Islands operation upon completion, because the results may not be generally useful otherwise. This matches the behaviour of the Unwrap operator. The nodes use the existing Vector socket type, and do not introduce a new 2D Vector type (see T92765). Differential Revision: https://developer.blender.org/D14389
This commit is contained in:
parent
d23818fcd9
commit
4593fb52cf
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue