Geometry Nodes: Operator to wrap a modifier's node group #104546
|
@ -6,7 +6,7 @@ from bpy.types import Operator
|
|||
from bpy.app.translations import pgettext_data as data_
|
||||
|
||||
|
||||
def geometry_node_group_empty_new():
|
||||
def geometry_node_group_empty_new(add_link=True):
|
||||
HooglyBoogly marked this conversation as resolved
|
||||
group = bpy.data.node_groups.new(data_("Geometry Nodes"), 'GeometryNodeTree')
|
||||
group.inputs.new('NodeSocketGeometry', data_("Geometry"))
|
||||
group.outputs.new('NodeSocketGeometry', data_("Geometry"))
|
||||
|
@ -20,7 +20,8 @@ def geometry_node_group_empty_new():
|
|||
input_node.location.x = -200 - input_node.width
|
||||
output_node.location.x = 200
|
||||
|
||||
group.links.new(output_node.inputs[0], input_node.outputs[0])
|
||||
if add_link:
|
||||
group.links.new(output_node.inputs[0], input_node.outputs[0])
|
||||
HooglyBoogly marked this conversation as resolved
Jacques Lucke
commented
Return the input and output node from the function, and move this line into Return the input and output node from the function, and move this line into `geometry_node_group_empty_new`.
|
||||
|
||||
return group
|
||||
|
||||
|
@ -84,7 +85,53 @@ class NewGeometryNodeTreeAssign(Operator):
|
|||
return {'FINISHED'}
|
||||
|
||||
|
||||
class CreateModifierWrapperGroup(Operator):
|
||||
"""Create a new node group wrapping the modifier's group"""
|
||||
|
||||
bl_idname = "node.new_geometry_node_group_wrapper"
|
||||
bl_label = "Create Wrapper Group"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return geometry_modifier_poll(context)
|
||||
HooglyBoogly marked this conversation as resolved
Jacques Lucke
commented
The name is still kind of ugly. Maybe something like The name is still kind of ugly. Maybe something like `Move to Nodes` (description: `Move inputs and outputs set in the modifier to a new node group`) or what Simon suggested.
Hans Goudey
commented
Yeah, good idea! Yeah, good idea!
|
||||
|
||||
def execute(self, context):
|
||||
if context.area.type == 'PROPERTIES':
|
||||
modifier = context.modifier
|
||||
HooglyBoogly marked this conversation as resolved
Jacques Lucke
commented
The class name and idname should be updated to respect the new label. The class name and idname should be updated to respect the new label.
|
||||
else:
|
||||
modifier = context.object.modifiers.active
|
||||
|
||||
if not modifier:
|
||||
return {'CANCELLED'}
|
||||
old_group = modifier.node_group
|
||||
if not old_group:
|
||||
return {'CANCELLED'}
|
||||
|
||||
group = geometry_node_group_empty_new(add_link=False)
|
||||
new_group_node = group.nodes.new("GeometryNodeGroup")
|
||||
new_group_node.node_tree = old_group
|
||||
|
||||
group_input_node = group.nodes["Group Input"]
|
||||
group_output_node = group.nodes["Group Output"]
|
||||
|
||||
for input in old_group.inputs:
|
||||
group.inputs.new()
|
||||
if hasattr(input, "default_value"):
|
||||
new_group_node.inputs[input.identifier].default_value = modifier[input.identifier]
|
||||
else:
|
||||
group.links.new(group_input_node.outputs[input.identifier], new_group_node.inputs[input.identifier])
|
||||
|
||||
HooglyBoogly marked this conversation as resolved
Jacques Lucke
commented
Avoid using Avoid using `input` as variable name because this is a built-in function in Python.
|
||||
for output in old_group.outputs:
|
||||
group.links.new(new_group_node.outputs[input.identifier], group_output_node.inputs[input.identifier])
|
||||
|
||||
modifier.node_group = group
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
classes = (
|
||||
NewGeometryNodesModifier,
|
||||
NewGeometryNodeTreeAssign,
|
||||
CreateModifierWrapperGroup,
|
||||
)
|
||||
|
|
|
@ -33,6 +33,7 @@ set(INC_SYS
|
|||
)
|
||||
|
||||
set(SRC
|
||||
geometry_nodes_wrapper.cc
|
||||
object_add.cc
|
||||
object_bake.cc
|
||||
object_bake_api.cc
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_modifier_types.h"
|
||||
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_idprop.hh"
|
||||
#include "BKE_lib_id.h"
|
||||
#include "BKE_node.h"
|
||||
#include "BKE_object.h"
|
||||
|
||||
#include "RNA_prototypes.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
#include "DEG_depsgraph_build.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
|
||||
#include "ED_object.h"
|
||||
#include "ED_screen.h"
|
||||
|
||||
#include "NOD_socket.h"
|
||||
|
||||
#include "MOD_nodes.h"
|
||||
|
||||
#include "object_intern.h"
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
/** \name Create Geometry Nodes Wrapper Object
|
||||
* \{ */
|
||||
|
||||
namespace blender::ed::object {
|
||||
|
||||
static eNodeSocketDatatype custom_data_type_to_socket_type(const eCustomDataType type)
|
||||
{
|
||||
switch (type) {
|
||||
case CD_PROP_FLOAT:
|
||||
return SOCK_FLOAT;
|
||||
case CD_PROP_INT32:
|
||||
return SOCK_INT;
|
||||
case CD_PROP_FLOAT3:
|
||||
return SOCK_VECTOR;
|
||||
case CD_PROP_BOOL:
|
||||
return SOCK_BOOLEAN;
|
||||
case CD_PROP_COLOR:
|
||||
return SOCK_RGBA;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return SOCK_FLOAT;
|
||||
}
|
||||
}
|
||||
|
||||
static eCustomDataType socket_type_to_custom_data_type(const eNodeSocketDatatype socket_type)
|
||||
{
|
||||
switch (socket_type) {
|
||||
case SOCK_FLOAT:
|
||||
return CD_PROP_FLOAT;
|
||||
case SOCK_INT:
|
||||
return CD_PROP_INT32;
|
||||
case SOCK_VECTOR:
|
||||
return CD_PROP_FLOAT3;
|
||||
case SOCK_BOOLEAN:
|
||||
return CD_PROP_BOOL;
|
||||
case SOCK_RGBA:
|
||||
return CD_PROP_COLOR;
|
||||
default:
|
||||
/* Fallback. */
|
||||
return CD_AUTO_FROM_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
static bool create_group_wrapper_poll(bContext *C)
|
||||
{
|
||||
return edit_modifier_poll_generic(C, &RNA_NodesModifier, 0, true, false);
|
||||
}
|
||||
|
||||
static int create_group_wrapper_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
// if context.area.type == 'PROPERTIES':
|
||||
// modifier = context.modifier
|
||||
// else:
|
||||
// modifier = context.object.modifiers.active
|
||||
|
||||
// if not modifier:
|
||||
// return {'CANCELLED'}
|
||||
// old_group = modifier.node_group
|
||||
// if not old_group:
|
||||
// return {'CANCELLED'}
|
||||
|
||||
// group = geometry_node_group_empty_new(add_link=False)
|
||||
// new_group_node = group.nodes.new("GeometryNodeGroup")
|
||||
// new_group_node.node_tree = old_group
|
||||
|
||||
// group_input_node = group.nodes["Group Input"]
|
||||
// group_output_node = group.nodes["Group Output"]
|
||||
|
||||
// for input in old_group.inputs:
|
||||
// group.inputs.new()
|
||||
// if hasattr(input, "default_value"):
|
||||
// new_group_node.inputs[input.identifier].default_value = modifier[input.identifier]
|
||||
// else:
|
||||
// group.links.new(group_input_node.outputs[input.identifier],
|
||||
// new_group_node.inputs[input.identifier])
|
||||
|
||||
// for output in old_group.outputs:
|
||||
// group.links.new(new_group_node.outputs[input.identifier],
|
||||
// group_output_node.inputs[input.identifier])
|
||||
|
||||
// modifier.node_group = group
|
||||
|
||||
// return {'FINISHED'}
|
||||
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Object *object = CTX_data_active_object(C);
|
||||
PointerRNA ptr = CTX_data_pointer_get_type(C, "modifier", &RNA_NodesModifier);
|
||||
NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr.data);
|
||||
if (!nmd) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
const bNodeTree *old_group = nmd->node_group;
|
||||
if (!old_group) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
bNodeTree *new_group = reinterpret_cast<bNodeTree *>(
|
||||
BKE_id_new(bmain, ID_NT, nmd->modifier.name));
|
||||
|
||||
old_group->ensure_topology_cache();
|
||||
|
||||
bNode *new_group_node = nodeAddNode(C, new_group, "GeometryNodeGroup");
|
||||
new_group->id = nmd->node_group;
|
||||
nodes::update_node_declaration_and_sockets(*new_group, *new_group_node);
|
||||
|
||||
for (const bNodeSocket *input : old_group->interface_inputs()) {
|
||||
bNodeSocket *group_node_socket = nodeFindSocket(new_group_node, SOCK_IN, input->identifier);
|
||||
ntreeAddSocketInterfaceFromSocket(new_group, new_group_node, group_node_socket);
|
||||
}
|
||||
for (const bNodeSocket *output : old_group->interface_outputs()) {
|
||||
bNodeSocket *group_node_socket = nodeFindSocket(new_group_node, SOCK_IN, output->identifier);
|
||||
ntreeAddSocketInterfaceFromSocket(new_group, new_group_node, group_node_socket);
|
||||
}
|
||||
|
||||
bNode *new_group_input_node = nodeAddNode(C, new_group, "NodeGroupInput");
|
||||
bNode *new_group_output_node = nodeAddNode(C, new_group, "NodeGroupInput");
|
||||
|
||||
for (const bNodeSocket *input : old_group->interface_inputs()) {
|
||||
if (const std::optional<StringRef> attribute = MOD_nodes_property_try_get_attribute(
|
||||
*nmd, input->identifier)) {
|
||||
bNode *named_attribute_node = nodeAddNode(C, new_group, "NodeGeometryInputNamedAttribute");
|
||||
const auto &storage = *static_cast<const NodeGeometryInputNamedAttribute *>(
|
||||
named_attribute_node->storage);
|
||||
|
||||
storage.data_type = socket_type_to_custom_data_type(input->type);
|
||||
named_attribute_node->typeinfo->updatefunc(new_group, named_attribute_node);
|
||||
|
||||
bNodeSocket *name_socket = nodeFindSocket(named_attribute_node, SOCK_IN, "Name");
|
||||
BLI_strncpy(name_socket->default_value_typed<bNodeSocketValueString>()->value,
|
||||
attribute->data(),
|
||||
attribute->size());
|
||||
nodeAddLink(new_group,
|
||||
named_attribute_node,
|
||||
bke::node_find_enabled_output_socket(*named_attribute_node, "Attribute"),
|
||||
new_group_node,
|
||||
nodeFindSocket(new_group_node, SOCK_IN, input->identifier));
|
||||
}
|
||||
else {
|
||||
IDProperty *prop = IDP_GetPropertyFromGroup(nmd->settings.properties, input->identifier);
|
||||
switch (eNodeSocketDatatype(input->type)) {
|
||||
case SOCK_FLOAT:
|
||||
break;
|
||||
case SOCK_VECTOR:
|
||||
break;
|
||||
case SOCK_RGBA:
|
||||
break;
|
||||
case SOCK_SHADER:
|
||||
break;
|
||||
case SOCK_BOOLEAN:
|
||||
break;
|
||||
case SOCK_INT:
|
||||
break;
|
||||
case SOCK_STRING:
|
||||
break;
|
||||
case SOCK_OBJECT:
|
||||
break;
|
||||
case SOCK_IMAGE:
|
||||
break;
|
||||
case SOCK_GEOMETRY:
|
||||
break;
|
||||
case SOCK_COLLECTION:
|
||||
break;
|
||||
case SOCK_TEXTURE:
|
||||
break;
|
||||
case SOCK_MATERIAL:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const bNodeSocket *output : old_group->interface_outputs()) {
|
||||
}
|
||||
|
||||
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
} // namespace blender::ed::object
|
||||
|
||||
void OBJECT_OT_new_geometry_node_group_wrapper(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Create Wrapper Geometry Group";
|
||||
ot->description = "Create a new node group that contains the modifier's node group as a node";
|
||||
ot->idname = "OBJECT_OT_new_geometry_node_group_wrapper";
|
||||
|
||||
ot->exec = blender::ed::object::create_group_wrapper_exec;
|
||||
ot->poll = blender::ed::object::create_group_wrapper_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
edit_modifier_properties(ot);
|
||||
}
|
||||
|
||||
/** \} */
|
|
@ -352,6 +352,10 @@ void OBJECT_OT_quadriflow_remesh(struct wmOperatorType *ot);
|
|||
void OBJECT_OT_data_transfer(struct wmOperatorType *ot);
|
||||
void OBJECT_OT_datalayout_transfer(struct wmOperatorType *ot);
|
||||
|
||||
/* geometry_nodes_wrapper.c */
|
||||
|
||||
void OBJECT_OT_new_geometry_node_group_wrapper(struct wmOperatorType *ot);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -279,6 +279,8 @@ void ED_operatortypes_object(void)
|
|||
WM_operatortype_append(OBJECT_OT_voxel_size_edit);
|
||||
|
||||
WM_operatortype_append(OBJECT_OT_quadriflow_remesh);
|
||||
|
||||
WM_operatortype_append(OBJECT_OT_new_geometry_node_group_wrapper);
|
||||
}
|
||||
|
||||
void ED_operatormacros_object(void)
|
||||
|
|
|
@ -5,6 +5,17 @@
|
|||
struct NodesModifierData;
|
||||
struct Object;
|
||||
|
||||
#ifdef __cplusplus
|
||||
# include <optional>
|
||||
|
||||
# include "BLI_string_ref.hh"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
std::optional<blender::StringRef> MOD_nodes_property_try_get_attribute(
|
||||
const NodesModifierData &nmd, const blender::StringRef identifier);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
|
@ -411,6 +411,27 @@ static bool logging_enabled(const ModifierEvalContext *ctx)
|
|||
static const std::string use_attribute_suffix = "_use_attribute";
|
||||
static const std::string attribute_name_suffix = "_attribute_name";
|
||||
|
||||
std::optional<StringRef> MOD_nodes_property_try_get_attribute(const NodesModifierData &nmd,
|
||||
const StringRef identifier)
|
||||
{
|
||||
if (!nmd.settings.properties) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::string use_attribute_name = identifier + use_attribute_suffix;
|
||||
const IDProperty *use_attribute_prop = IDP_GetPropertyFromGroup(nmd.settings.properties,
|
||||
use_attribute_name.c_str());
|
||||
if (!use_attribute_prop) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (IDP_Int(use_attribute_prop) == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::string name_prop_name = identifier + attribute_name_suffix;
|
||||
const IDProperty *name_prop = IDP_GetPropertyFromGroup(nmd.settings.properties,
|
||||
name_prop_name.c_str());
|
||||
return IDP_String(name_prop);
|
||||
}
|
||||
|
||||
/**
|
||||
* \return Whether using an attribute to input values of this type is supported.
|
||||
*/
|
||||
|
|
|
@ -274,6 +274,17 @@ static void modifier_ops_extra_draw(bContext *C, uiLayout *layout, void *md_v)
|
|||
if (!md->next) {
|
||||
uiLayoutSetEnabled(row, false);
|
||||
}
|
||||
|
||||
if (md->type == eModifierType_Nodes) {
|
||||
uiItemFullO(layout,
|
||||
"OBJECT_OT_new_geometry_node_group_wrapper",
|
||||
IFACE_("Create Wrapper Group"),
|
||||
ICON_NONE,
|
||||
NULL,
|
||||
WM_OP_INVOKE_DEFAULT,
|
||||
0,
|
||||
&op_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void modifier_panel_header(const bContext *C, Panel *panel)
|
||||
|
|
Loading…
Reference in New Issue
Better use two separate functions that call a common function instead of adding optional parameters to this one.