This allows choosing material and texture sockets for the group input node in the modifier. Note that currently grease pencil materials are displayed in the list, even though grease pencil data is not supported yet by geometry nodes. That is more complicated to fix in this case, since we use IDProperties to store the dynamic exposed inputs. Differential Revision: https://developer.blender.org/D11393
1291 lines
47 KiB
C++
1291 lines
47 KiB
C++
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2005 by the Blender Foundation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup modifiers
|
|
*/
|
|
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_float3.hh"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_multi_value_map.hh"
|
|
#include "BLI_set.hh"
|
|
#include "BLI_string.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DNA_collection_types.h"
|
|
#include "DNA_defaults.h"
|
|
#include "DNA_material_types.h"
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_modifier_types.h"
|
|
#include "DNA_node_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_pointcloud_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_screen_types.h"
|
|
#include "DNA_space_types.h"
|
|
#include "DNA_windowmanager_types.h"
|
|
|
|
#include "BKE_customdata.h"
|
|
#include "BKE_geometry_set_instances.hh"
|
|
#include "BKE_global.h"
|
|
#include "BKE_idprop.h"
|
|
#include "BKE_lib_query.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_mesh.h"
|
|
#include "BKE_modifier.h"
|
|
#include "BKE_node_ui_storage.hh"
|
|
#include "BKE_object.h"
|
|
#include "BKE_pointcloud.h"
|
|
#include "BKE_screen.h"
|
|
#include "BKE_simulation.h"
|
|
#include "BKE_workspace.h"
|
|
|
|
#include "BLO_read_write.h"
|
|
|
|
#include "UI_interface.h"
|
|
#include "UI_resources.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_enum_types.h"
|
|
|
|
#include "DEG_depsgraph_build.h"
|
|
#include "DEG_depsgraph_query.h"
|
|
|
|
#include "MOD_modifiertypes.h"
|
|
#include "MOD_nodes.h"
|
|
#include "MOD_nodes_evaluator.hh"
|
|
#include "MOD_ui_common.h"
|
|
|
|
#include "ED_spreadsheet.h"
|
|
|
|
#include "NOD_derived_node_tree.hh"
|
|
#include "NOD_geometry.h"
|
|
#include "NOD_node_tree_multi_function.hh"
|
|
|
|
using blender::float3;
|
|
using blender::FunctionRef;
|
|
using blender::IndexRange;
|
|
using blender::Map;
|
|
using blender::Set;
|
|
using blender::Span;
|
|
using blender::StringRef;
|
|
using blender::StringRefNull;
|
|
using blender::Vector;
|
|
using blender::fn::GMutablePointer;
|
|
using blender::fn::GPointer;
|
|
using blender::nodes::GeoNodeExecParams;
|
|
using namespace blender::fn::multi_function_types;
|
|
using namespace blender::nodes::derived_node_tree_types;
|
|
|
|
static void initData(ModifierData *md)
|
|
{
|
|
NodesModifierData *nmd = (NodesModifierData *)md;
|
|
|
|
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(nmd, modifier));
|
|
|
|
MEMCPY_STRUCT_AFTER(nmd, DNA_struct_default_get(NodesModifierData), modifier);
|
|
}
|
|
|
|
static void addIdsUsedBySocket(const ListBase *sockets, Set<ID *> &ids)
|
|
{
|
|
LISTBASE_FOREACH (const bNodeSocket *, socket, sockets) {
|
|
if (socket->type == SOCK_OBJECT) {
|
|
Object *object = ((bNodeSocketValueObject *)socket->default_value)->value;
|
|
if (object != nullptr) {
|
|
ids.add(&object->id);
|
|
}
|
|
}
|
|
else if (socket->type == SOCK_COLLECTION) {
|
|
Collection *collection = ((bNodeSocketValueCollection *)socket->default_value)->value;
|
|
if (collection != nullptr) {
|
|
ids.add(&collection->id);
|
|
}
|
|
}
|
|
else if (socket->type == SOCK_MATERIAL) {
|
|
Material *material = ((bNodeSocketValueMaterial *)socket->default_value)->value;
|
|
if (material != nullptr) {
|
|
ids.add(&material->id);
|
|
}
|
|
}
|
|
else if (socket->type == SOCK_TEXTURE) {
|
|
Tex *texture = ((bNodeSocketValueTexture *)socket->default_value)->value;
|
|
if (texture != nullptr) {
|
|
ids.add(&texture->id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void find_used_ids_from_nodes(const bNodeTree &tree, Set<ID *> &ids)
|
|
{
|
|
Set<const bNodeTree *> handled_groups;
|
|
|
|
LISTBASE_FOREACH (const bNode *, node, &tree.nodes) {
|
|
addIdsUsedBySocket(&node->inputs, ids);
|
|
addIdsUsedBySocket(&node->outputs, ids);
|
|
|
|
if (ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP)) {
|
|
const bNodeTree *group = (bNodeTree *)node->id;
|
|
if (group != nullptr && handled_groups.add(group)) {
|
|
find_used_ids_from_nodes(*group, ids);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void find_used_ids_from_settings(const NodesModifierSettings &settings, Set<ID *> &ids)
|
|
{
|
|
IDP_foreach_property(
|
|
settings.properties,
|
|
IDP_TYPE_FILTER_ID,
|
|
[](IDProperty *property, void *user_data) {
|
|
Set<ID *> *ids = (Set<ID *> *)user_data;
|
|
ID *id = IDP_Id(property);
|
|
if (id != nullptr) {
|
|
ids->add(id);
|
|
}
|
|
},
|
|
&ids);
|
|
}
|
|
|
|
/* We don't know exactly what attributes from the other object we will need. */
|
|
static const CustomData_MeshMasks dependency_data_mask{CD_MASK_PROP_ALL | CD_MASK_MDEFORMVERT,
|
|
CD_MASK_PROP_ALL,
|
|
CD_MASK_PROP_ALL,
|
|
CD_MASK_PROP_ALL,
|
|
CD_MASK_PROP_ALL};
|
|
|
|
static void add_collection_relation(const ModifierUpdateDepsgraphContext *ctx,
|
|
Collection &collection)
|
|
{
|
|
DEG_add_collection_geometry_relation(ctx->node, &collection, "Nodes Modifier");
|
|
DEG_add_collection_geometry_customdata_mask(ctx->node, &collection, &dependency_data_mask);
|
|
}
|
|
|
|
static void add_object_relation(const ModifierUpdateDepsgraphContext *ctx, Object &object)
|
|
{
|
|
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_TRANSFORM, "Nodes Modifier");
|
|
if (&(ID &)object != &ctx->object->id) {
|
|
if (object.type == OB_EMPTY && object.instance_collection != nullptr) {
|
|
add_collection_relation(ctx, *object.instance_collection);
|
|
}
|
|
else if (DEG_object_has_geometry_component(&object)) {
|
|
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_GEOMETRY, "Nodes Modifier");
|
|
DEG_add_customdata_mask(ctx->node, &object, &dependency_data_mask);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
DEG_add_modifier_to_transform_relation(ctx->node, "Nodes Modifier");
|
|
if (nmd->node_group != nullptr) {
|
|
DEG_add_node_tree_relation(ctx->node, nmd->node_group, "Nodes Modifier");
|
|
|
|
Set<ID *> used_ids;
|
|
find_used_ids_from_settings(nmd->settings, used_ids);
|
|
find_used_ids_from_nodes(*nmd->node_group, used_ids);
|
|
for (ID *id : used_ids) {
|
|
switch ((ID_Type)GS(id->name)) {
|
|
case ID_OB: {
|
|
Object *object = reinterpret_cast<Object *>(id);
|
|
add_object_relation(ctx, *object);
|
|
break;
|
|
}
|
|
case ID_GR: {
|
|
Collection *collection = reinterpret_cast<Collection *>(id);
|
|
add_collection_relation(ctx, *collection);
|
|
break;
|
|
}
|
|
case ID_TE: {
|
|
DEG_add_generic_id_relation(ctx->node, id, "Nodes Modifier");
|
|
}
|
|
default: {
|
|
/* Purposefully don't add relations for materials. While there are material sockets,
|
|
* the pointers are only passed around as handles rather than dereferenced. */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
walk(userData, ob, (ID **)&nmd->node_group, IDWALK_CB_USER);
|
|
|
|
struct ForeachSettingData {
|
|
IDWalkFunc walk;
|
|
void *userData;
|
|
Object *ob;
|
|
} settings = {walk, userData, ob};
|
|
|
|
IDP_foreach_property(
|
|
nmd->settings.properties,
|
|
IDP_TYPE_FILTER_ID,
|
|
[](IDProperty *id_prop, void *user_data) {
|
|
ForeachSettingData *settings = (ForeachSettingData *)user_data;
|
|
settings->walk(
|
|
settings->userData, settings->ob, (ID **)&id_prop->data.pointer, IDWALK_CB_USER);
|
|
},
|
|
&settings);
|
|
}
|
|
|
|
static void foreachTexLink(ModifierData *md, Object *ob, TexWalkFunc walk, void *userData)
|
|
{
|
|
walk(userData, ob, md, "texture");
|
|
}
|
|
|
|
static bool isDisabled(const struct Scene *UNUSED(scene),
|
|
ModifierData *md,
|
|
bool UNUSED(useRenderParams))
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
|
|
if (nmd->node_group == nullptr) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool logging_enabled(const ModifierEvalContext *ctx)
|
|
{
|
|
if (!DEG_is_active(ctx->depsgraph)) {
|
|
return false;
|
|
}
|
|
if ((ctx->flag & MOD_APPLY_ORCO) != 0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This code is responsible for creating the new property and also creating the group of
|
|
* properties in the prop_ui_container group for the UI info, the mapping for which is
|
|
* scattered about in RNA_access.c.
|
|
*
|
|
* TODO(Hans): Codify this with some sort of table or refactor IDProperty use in RNA_access.c.
|
|
*/
|
|
struct SocketPropertyType {
|
|
/* Create the actual property used to store the data for the modifier. */
|
|
IDProperty *(*create_prop)(const bNodeSocket &socket, const char *name);
|
|
/* Reused to build the "soft_min" property too. */
|
|
IDProperty *(*create_min_ui_prop)(const bNodeSocket &socket, const char *name);
|
|
/* Reused to build the "soft_max" property too. */
|
|
IDProperty *(*create_max_ui_prop)(const bNodeSocket &socket, const char *name);
|
|
/* This uses the same values as #create_prop, but sometimes the type is different, so it can't
|
|
* be the same function. */
|
|
IDProperty *(*create_default_ui_prop)(const bNodeSocket &socket, const char *name);
|
|
PropertyType (*rna_subtype_get)(const bNodeSocket &socket);
|
|
bool (*is_correct_type)(const IDProperty &property);
|
|
void (*init_cpp_value)(const IDProperty &property, void *r_value);
|
|
};
|
|
|
|
static IDProperty *socket_add_property(IDProperty *settings_prop_group,
|
|
IDProperty *ui_container,
|
|
const SocketPropertyType &property_type,
|
|
const bNodeSocket &socket)
|
|
{
|
|
const char *new_prop_name = socket.identifier;
|
|
/* Add the property actually storing the data to the modifier's group. */
|
|
IDProperty *prop = property_type.create_prop(socket, new_prop_name);
|
|
IDP_AddToGroup(settings_prop_group, prop);
|
|
|
|
prop->flag |= IDP_FLAG_OVERRIDABLE_LIBRARY;
|
|
|
|
/* Make the group in the UI container group to hold the property's UI settings. */
|
|
IDProperty *prop_ui_group;
|
|
{
|
|
IDPropertyTemplate idprop = {0};
|
|
prop_ui_group = IDP_New(IDP_GROUP, &idprop, new_prop_name);
|
|
IDP_AddToGroup(ui_container, prop_ui_group);
|
|
}
|
|
|
|
/* Set property description (tooltip). */
|
|
IDPropertyTemplate property_description_template;
|
|
property_description_template.string.str = socket.description;
|
|
property_description_template.string.len = BLI_strnlen(socket.description, MAX_NAME) + 1;
|
|
property_description_template.string.subtype = IDP_STRING_SUB_UTF8;
|
|
IDProperty *description = IDP_New(IDP_STRING, &property_description_template, "description");
|
|
IDP_AddToGroup(prop_ui_group, description);
|
|
|
|
/* Create the properties for the socket's UI settings. */
|
|
if (property_type.create_min_ui_prop != nullptr) {
|
|
IDP_AddToGroup(prop_ui_group, property_type.create_min_ui_prop(socket, "min"));
|
|
IDP_AddToGroup(prop_ui_group, property_type.create_min_ui_prop(socket, "soft_min"));
|
|
}
|
|
if (property_type.create_max_ui_prop != nullptr) {
|
|
IDP_AddToGroup(prop_ui_group, property_type.create_max_ui_prop(socket, "max"));
|
|
IDP_AddToGroup(prop_ui_group, property_type.create_max_ui_prop(socket, "soft_max"));
|
|
}
|
|
if (property_type.create_default_ui_prop != nullptr) {
|
|
IDP_AddToGroup(prop_ui_group, property_type.create_default_ui_prop(socket, "default"));
|
|
}
|
|
if (property_type.rna_subtype_get != nullptr) {
|
|
const char *subtype_identifier = nullptr;
|
|
RNA_enum_identifier(rna_enum_property_subtype_items,
|
|
property_type.rna_subtype_get(socket),
|
|
&subtype_identifier);
|
|
|
|
if (subtype_identifier != nullptr) {
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.string.str = subtype_identifier;
|
|
idprop.string.len = BLI_strnlen(subtype_identifier, MAX_NAME) + 1;
|
|
IDP_AddToGroup(prop_ui_group, IDP_New(IDP_STRING, &idprop, "subtype"));
|
|
}
|
|
}
|
|
|
|
return prop;
|
|
}
|
|
|
|
static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bsocket)
|
|
{
|
|
switch (bsocket.type) {
|
|
case SOCK_FLOAT: {
|
|
static const SocketPropertyType float_type = {
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.f = value->value;
|
|
return IDP_New(IDP_FLOAT, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.d = value->min;
|
|
return IDP_New(IDP_DOUBLE, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.d = value->max;
|
|
return IDP_New(IDP_DOUBLE, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.d = value->value;
|
|
return IDP_New(IDP_DOUBLE, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &socket) {
|
|
return (PropertyType)((bNodeSocketValueFloat *)socket.default_value)->subtype;
|
|
},
|
|
[](const IDProperty &property) { return ELEM(property.type, IDP_FLOAT, IDP_DOUBLE); },
|
|
[](const IDProperty &property, void *r_value) {
|
|
if (property.type == IDP_FLOAT) {
|
|
*(float *)r_value = IDP_Float(&property);
|
|
}
|
|
else if (property.type == IDP_DOUBLE) {
|
|
*(float *)r_value = (float)IDP_Double(&property);
|
|
}
|
|
},
|
|
};
|
|
return &float_type;
|
|
}
|
|
case SOCK_INT: {
|
|
static const SocketPropertyType int_type = {
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.i = value->value;
|
|
return IDP_New(IDP_INT, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.i = value->min;
|
|
return IDP_New(IDP_INT, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.i = value->max;
|
|
return IDP_New(IDP_INT, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.i = value->value;
|
|
return IDP_New(IDP_INT, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &socket) {
|
|
return (PropertyType)((bNodeSocketValueInt *)socket.default_value)->subtype;
|
|
},
|
|
[](const IDProperty &property) { return property.type == IDP_INT; },
|
|
[](const IDProperty &property, void *r_value) { *(int *)r_value = IDP_Int(&property); },
|
|
};
|
|
return &int_type;
|
|
}
|
|
case SOCK_VECTOR: {
|
|
static const SocketPropertyType vector_type = {
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.array.len = 3;
|
|
idprop.array.type = IDP_FLOAT;
|
|
IDProperty *property = IDP_New(IDP_ARRAY, &idprop, name);
|
|
copy_v3_v3((float *)IDP_Array(property), value->value);
|
|
return property;
|
|
},
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.d = value->min;
|
|
return IDP_New(IDP_DOUBLE, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.d = value->max;
|
|
return IDP_New(IDP_DOUBLE, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.array.len = 3;
|
|
idprop.array.type = IDP_FLOAT;
|
|
IDProperty *property = IDP_New(IDP_ARRAY, &idprop, name);
|
|
copy_v3_v3((float *)IDP_Array(property), value->value);
|
|
return property;
|
|
},
|
|
[](const bNodeSocket &socket) {
|
|
return (PropertyType)((bNodeSocketValueVector *)socket.default_value)->subtype;
|
|
},
|
|
[](const IDProperty &property) {
|
|
return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT &&
|
|
property.len == 3;
|
|
},
|
|
[](const IDProperty &property, void *r_value) {
|
|
copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property));
|
|
},
|
|
};
|
|
return &vector_type;
|
|
}
|
|
case SOCK_BOOLEAN: {
|
|
static const SocketPropertyType boolean_type = {
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.i = value->value != 0;
|
|
return IDP_New(IDP_INT, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &UNUSED(socket), const char *name) {
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.i = 0;
|
|
return IDP_New(IDP_INT, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &UNUSED(socket), const char *name) {
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.i = 1;
|
|
return IDP_New(IDP_INT, &idprop, name);
|
|
},
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.i = value->value != 0;
|
|
return IDP_New(IDP_INT, &idprop, name);
|
|
},
|
|
nullptr,
|
|
[](const IDProperty &property) { return property.type == IDP_INT; },
|
|
[](const IDProperty &property, void *r_value) {
|
|
*(bool *)r_value = IDP_Int(&property) != 0;
|
|
},
|
|
};
|
|
return &boolean_type;
|
|
}
|
|
case SOCK_STRING: {
|
|
static const SocketPropertyType string_type = {
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value;
|
|
return IDP_NewString(
|
|
value->value, name, BLI_strnlen(value->value, sizeof(value->value)) + 1);
|
|
},
|
|
nullptr,
|
|
nullptr,
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value;
|
|
return IDP_NewString(
|
|
value->value, name, BLI_strnlen(value->value, sizeof(value->value)) + 1);
|
|
},
|
|
nullptr,
|
|
[](const IDProperty &property) { return property.type == IDP_STRING; },
|
|
[](const IDProperty &property, void *r_value) {
|
|
new (r_value) std::string(IDP_String(&property));
|
|
},
|
|
};
|
|
return &string_type;
|
|
}
|
|
case SOCK_OBJECT: {
|
|
static const SocketPropertyType object_type = {
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueObject *value = (bNodeSocketValueObject *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.id = (ID *)value->value;
|
|
return IDP_New(IDP_ID, &idprop, name);
|
|
},
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
[](const IDProperty &property) { return property.type == IDP_ID; },
|
|
[](const IDProperty &property, void *r_value) {
|
|
ID *id = IDP_Id(&property);
|
|
Object *object = (id && GS(id->name) == ID_OB) ? (Object *)id : nullptr;
|
|
*(Object **)r_value = object;
|
|
},
|
|
};
|
|
return &object_type;
|
|
}
|
|
case SOCK_COLLECTION: {
|
|
static const SocketPropertyType collection_type = {
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueCollection *value = (bNodeSocketValueCollection *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.id = (ID *)value->value;
|
|
return IDP_New(IDP_ID, &idprop, name);
|
|
},
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
[](const IDProperty &property) { return property.type == IDP_ID; },
|
|
[](const IDProperty &property, void *r_value) {
|
|
ID *id = IDP_Id(&property);
|
|
Collection *collection = (id && GS(id->name) == ID_GR) ? (Collection *)id : nullptr;
|
|
*(Collection **)r_value = collection;
|
|
},
|
|
};
|
|
return &collection_type;
|
|
}
|
|
case SOCK_TEXTURE: {
|
|
static const SocketPropertyType collection_type = {
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueTexture *value = (bNodeSocketValueTexture *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.id = (ID *)value->value;
|
|
return IDP_New(IDP_ID, &idprop, name);
|
|
},
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
[](const IDProperty &property) { return property.type == IDP_ID; },
|
|
[](const IDProperty &property, void *r_value) {
|
|
ID *id = IDP_Id(&property);
|
|
Tex *texture = (id && GS(id->name) == ID_TE) ? (Tex *)id : nullptr;
|
|
*(Tex **)r_value = texture;
|
|
},
|
|
};
|
|
return &collection_type;
|
|
}
|
|
case SOCK_MATERIAL: {
|
|
static const SocketPropertyType collection_type = {
|
|
[](const bNodeSocket &socket, const char *name) {
|
|
bNodeSocketValueMaterial *value = (bNodeSocketValueMaterial *)socket.default_value;
|
|
IDPropertyTemplate idprop = {0};
|
|
idprop.id = (ID *)value->value;
|
|
return IDP_New(IDP_ID, &idprop, name);
|
|
},
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
[](const IDProperty &property) { return property.type == IDP_ID; },
|
|
[](const IDProperty &property, void *r_value) {
|
|
ID *id = IDP_Id(&property);
|
|
Material *material = (id && GS(id->name) == ID_MA) ? (Material *)id : nullptr;
|
|
*(Material **)r_value = material;
|
|
},
|
|
};
|
|
return &collection_type;
|
|
}
|
|
default: {
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rebuild the list of properties based on the sockets exposed as the modifier's node group
|
|
* inputs. If any properties correspond to the old properties by name and type, carry over
|
|
* the values.
|
|
*/
|
|
void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
|
|
{
|
|
if (nmd->node_group == nullptr) {
|
|
return;
|
|
}
|
|
|
|
IDProperty *old_properties = nmd->settings.properties;
|
|
|
|
{
|
|
IDPropertyTemplate idprop = {0};
|
|
nmd->settings.properties = IDP_New(IDP_GROUP, &idprop, "Nodes Modifier Settings");
|
|
}
|
|
|
|
IDProperty *ui_container_group;
|
|
{
|
|
IDPropertyTemplate idprop = {0};
|
|
ui_container_group = IDP_New(IDP_GROUP, &idprop, "_RNA_UI");
|
|
IDP_AddToGroup(nmd->settings.properties, ui_container_group);
|
|
}
|
|
|
|
LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) {
|
|
const SocketPropertyType *property_type = get_socket_property_type(*socket);
|
|
if (property_type == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
IDProperty *new_prop = socket_add_property(
|
|
nmd->settings.properties, ui_container_group, *property_type, *socket);
|
|
|
|
if (old_properties != nullptr) {
|
|
IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, socket->identifier);
|
|
if (old_prop != nullptr && property_type->is_correct_type(*old_prop)) {
|
|
IDP_CopyPropertyContent(new_prop, old_prop);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (old_properties != nullptr) {
|
|
IDP_FreeProperty(old_properties);
|
|
}
|
|
|
|
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
|
}
|
|
|
|
void MOD_nodes_init(Main *bmain, NodesModifierData *nmd)
|
|
{
|
|
bNodeTree *ntree = ntreeAddTree(bmain, "Geometry Nodes", ntreeType_Geometry->idname);
|
|
nmd->node_group = ntree;
|
|
|
|
ntreeAddSocketInterface(ntree, SOCK_IN, "NodeSocketGeometry", "Geometry");
|
|
ntreeAddSocketInterface(ntree, SOCK_OUT, "NodeSocketGeometry", "Geometry");
|
|
|
|
bNode *group_input_node = nodeAddStaticNode(nullptr, ntree, NODE_GROUP_INPUT);
|
|
bNode *group_output_node = nodeAddStaticNode(nullptr, ntree, NODE_GROUP_OUTPUT);
|
|
|
|
nodeSetSelected(group_input_node, false);
|
|
nodeSetSelected(group_output_node, false);
|
|
|
|
group_input_node->locx = -200 - group_input_node->width;
|
|
group_output_node->locx = 200;
|
|
group_output_node->flag |= NODE_DO_OUTPUT;
|
|
|
|
nodeAddLink(ntree,
|
|
group_output_node,
|
|
(bNodeSocket *)group_output_node->inputs.first,
|
|
group_input_node,
|
|
(bNodeSocket *)group_input_node->outputs.first);
|
|
|
|
ntreeUpdateTree(bmain, ntree);
|
|
}
|
|
|
|
static void initialize_group_input(NodesModifierData &nmd,
|
|
const bNodeSocket &socket,
|
|
const CPPType &cpp_type,
|
|
void *r_value)
|
|
{
|
|
const SocketPropertyType *property_type = get_socket_property_type(socket);
|
|
if (property_type == nullptr) {
|
|
cpp_type.copy_to_uninitialized(cpp_type.default_value(), r_value);
|
|
return;
|
|
}
|
|
if (nmd.settings.properties == nullptr) {
|
|
blender::nodes::socket_cpp_value_get(socket, r_value);
|
|
return;
|
|
}
|
|
const IDProperty *property = IDP_GetPropertyFromGroup(nmd.settings.properties,
|
|
socket.identifier);
|
|
if (property == nullptr) {
|
|
blender::nodes::socket_cpp_value_get(socket, r_value);
|
|
return;
|
|
}
|
|
if (!property_type->is_correct_type(*property)) {
|
|
blender::nodes::socket_cpp_value_get(socket, r_value);
|
|
return;
|
|
}
|
|
property_type->init_cpp_value(*property, r_value);
|
|
}
|
|
|
|
static void reset_tree_ui_storage(Span<const blender::nodes::NodeTreeRef *> trees,
|
|
const Object &object,
|
|
const ModifierData &modifier)
|
|
{
|
|
const NodeTreeEvaluationContext context = {object, modifier};
|
|
|
|
for (const blender::nodes::NodeTreeRef *tree : trees) {
|
|
bNodeTree *btree_cow = tree->btree();
|
|
bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow);
|
|
BKE_nodetree_ui_storage_free_for_context(*btree_original, context);
|
|
}
|
|
}
|
|
|
|
static Vector<SpaceSpreadsheet *> find_spreadsheet_editors(Main *bmain)
|
|
{
|
|
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
|
|
if (wm == nullptr) {
|
|
return {};
|
|
}
|
|
Vector<SpaceSpreadsheet *> spreadsheets;
|
|
LISTBASE_FOREACH (wmWindow *, window, &wm->windows) {
|
|
bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
|
|
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
|
|
SpaceLink *sl = (SpaceLink *)area->spacedata.first;
|
|
if (sl->spacetype == SPACE_SPREADSHEET) {
|
|
spreadsheets.append((SpaceSpreadsheet *)sl);
|
|
}
|
|
}
|
|
}
|
|
return spreadsheets;
|
|
}
|
|
|
|
using PreviewSocketMap = blender::MultiValueMap<DSocket, uint64_t>;
|
|
|
|
static DSocket try_find_preview_socket_in_node(const DNode node)
|
|
{
|
|
for (const SocketRef *socket : node->outputs()) {
|
|
if (socket->bsocket()->type == SOCK_GEOMETRY) {
|
|
return {node.context(), socket};
|
|
}
|
|
}
|
|
for (const SocketRef *socket : node->inputs()) {
|
|
if (socket->bsocket()->type == SOCK_GEOMETRY &&
|
|
(socket->bsocket()->flag & SOCK_MULTI_INPUT) == 0) {
|
|
return {node.context(), socket};
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static DSocket try_get_socket_to_preview_for_spreadsheet(SpaceSpreadsheet *sspreadsheet,
|
|
NodesModifierData *nmd,
|
|
const ModifierEvalContext *ctx,
|
|
const DerivedNodeTree &tree)
|
|
{
|
|
Vector<SpreadsheetContext *> context_path = sspreadsheet->context_path;
|
|
if (context_path.size() < 3) {
|
|
return {};
|
|
}
|
|
if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) {
|
|
return {};
|
|
}
|
|
if (context_path[1]->type != SPREADSHEET_CONTEXT_MODIFIER) {
|
|
return {};
|
|
}
|
|
SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context_path[0];
|
|
if (object_context->object != DEG_get_original_object(ctx->object)) {
|
|
return {};
|
|
}
|
|
SpreadsheetContextModifier *modifier_context = (SpreadsheetContextModifier *)context_path[1];
|
|
if (StringRef(modifier_context->modifier_name) != nmd->modifier.name) {
|
|
return {};
|
|
}
|
|
for (SpreadsheetContext *context : context_path.as_span().drop_front(2)) {
|
|
if (context->type != SPREADSHEET_CONTEXT_NODE) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
Span<SpreadsheetContextNode *> nested_group_contexts =
|
|
context_path.as_span().drop_front(2).drop_back(1).cast<SpreadsheetContextNode *>();
|
|
SpreadsheetContextNode *last_context = (SpreadsheetContextNode *)context_path.last();
|
|
|
|
const DTreeContext *context = &tree.root_context();
|
|
for (SpreadsheetContextNode *node_context : nested_group_contexts) {
|
|
const NodeTreeRef &tree_ref = context->tree();
|
|
const NodeRef *found_node = nullptr;
|
|
for (const NodeRef *node_ref : tree_ref.nodes()) {
|
|
if (node_ref->name() == node_context->node_name) {
|
|
found_node = node_ref;
|
|
break;
|
|
}
|
|
}
|
|
if (found_node == nullptr) {
|
|
return {};
|
|
}
|
|
context = context->child_context(*found_node);
|
|
if (context == nullptr) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
const NodeTreeRef &tree_ref = context->tree();
|
|
for (const NodeRef *node_ref : tree_ref.nodes()) {
|
|
if (node_ref->name() == last_context->node_name) {
|
|
return try_find_preview_socket_in_node({context, node_ref});
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static void find_sockets_to_preview(NodesModifierData *nmd,
|
|
const ModifierEvalContext *ctx,
|
|
const DerivedNodeTree &tree,
|
|
PreviewSocketMap &r_sockets_to_preview)
|
|
{
|
|
Main *bmain = DEG_get_bmain(ctx->depsgraph);
|
|
|
|
/* Based on every visible spreadsheet context path, get a list of sockets that need to have their
|
|
* intermediate geometries cached for display. */
|
|
Vector<SpaceSpreadsheet *> spreadsheets = find_spreadsheet_editors(bmain);
|
|
for (SpaceSpreadsheet *sspreadsheet : spreadsheets) {
|
|
const DSocket socket = try_get_socket_to_preview_for_spreadsheet(sspreadsheet, nmd, ctx, tree);
|
|
if (socket) {
|
|
const uint64_t key = ED_spreadsheet_context_path_hash(sspreadsheet);
|
|
r_sockets_to_preview.add_non_duplicates(socket, key);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void log_preview_socket_value(const Span<GPointer> values,
|
|
Object *object,
|
|
Span<uint64_t> keys)
|
|
{
|
|
GeometrySet geometry_set = *(const GeometrySet *)values[0].get();
|
|
geometry_set.ensure_owns_direct_data();
|
|
for (uint64_t key : keys) {
|
|
BKE_object_preview_geometry_set_add(object, key, new GeometrySet(geometry_set));
|
|
}
|
|
}
|
|
|
|
static void log_ui_hints(const DSocket socket,
|
|
const Span<GPointer> values,
|
|
Object *self_object,
|
|
NodesModifierData *nmd)
|
|
{
|
|
const DNode node = socket.node();
|
|
if (node->is_reroute_node() || socket->typeinfo()->type != SOCK_GEOMETRY) {
|
|
return;
|
|
}
|
|
bNodeTree *btree_cow = node->btree();
|
|
bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow);
|
|
const NodeTreeEvaluationContext context{*self_object, nmd->modifier};
|
|
for (const GPointer &data : values) {
|
|
if (data.type() == &CPPType::get<GeometrySet>()) {
|
|
const GeometrySet &geometry_set = *(const GeometrySet *)data.get();
|
|
blender::bke::geometry_set_instances_attribute_foreach(
|
|
geometry_set,
|
|
[&](StringRefNull attribute_name, const AttributeMetaData &meta_data) {
|
|
BKE_nodetree_attribute_hint_add(*btree_original,
|
|
context,
|
|
*node->bnode(),
|
|
attribute_name,
|
|
meta_data.domain,
|
|
meta_data.data_type);
|
|
return true;
|
|
},
|
|
8);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Evaluate a node group to compute the output geometry.
|
|
* Currently, this uses a fairly basic and inefficient algorithm that might compute things more
|
|
* often than necessary. It's going to be replaced soon.
|
|
*/
|
|
static GeometrySet compute_geometry(const DerivedNodeTree &tree,
|
|
Span<const NodeRef *> group_input_nodes,
|
|
const InputSocketRef &socket_to_compute,
|
|
GeometrySet input_geometry_set,
|
|
NodesModifierData *nmd,
|
|
const ModifierEvalContext *ctx)
|
|
{
|
|
blender::ResourceScope scope;
|
|
blender::LinearAllocator<> &allocator = scope.linear_allocator();
|
|
blender::nodes::MultiFunctionByNode mf_by_node = get_multi_function_per_node(tree, scope);
|
|
|
|
Map<DOutputSocket, GMutablePointer> group_inputs;
|
|
|
|
const DTreeContext *root_context = &tree.root_context();
|
|
for (const NodeRef *group_input_node : group_input_nodes) {
|
|
Span<const OutputSocketRef *> group_input_sockets = group_input_node->outputs().drop_back(1);
|
|
if (group_input_sockets.is_empty()) {
|
|
continue;
|
|
}
|
|
|
|
Span<const OutputSocketRef *> remaining_input_sockets = group_input_sockets;
|
|
|
|
/* If the group expects a geometry as first input, use the geometry that has been passed to
|
|
* modifier. */
|
|
const OutputSocketRef *first_input_socket = group_input_sockets[0];
|
|
if (first_input_socket->bsocket()->type == SOCK_GEOMETRY) {
|
|
GeometrySet *geometry_set_in =
|
|
allocator.construct<GeometrySet>(input_geometry_set).release();
|
|
group_inputs.add_new({root_context, first_input_socket}, geometry_set_in);
|
|
remaining_input_sockets = remaining_input_sockets.drop_front(1);
|
|
}
|
|
|
|
/* Initialize remaining group inputs. */
|
|
for (const OutputSocketRef *socket : remaining_input_sockets) {
|
|
const CPPType &cpp_type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo());
|
|
void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment());
|
|
initialize_group_input(*nmd, *socket->bsocket(), cpp_type, value_in);
|
|
group_inputs.add_new({root_context, socket}, {cpp_type, value_in});
|
|
}
|
|
}
|
|
|
|
/* Don't keep a reference to the input geometry components to avoid copies during evaluation. */
|
|
input_geometry_set.clear();
|
|
|
|
Vector<DInputSocket> group_outputs;
|
|
group_outputs.append({root_context, &socket_to_compute});
|
|
|
|
PreviewSocketMap preview_sockets;
|
|
find_sockets_to_preview(nmd, ctx, tree, preview_sockets);
|
|
|
|
auto log_socket_value = [&](const DSocket socket, const Span<GPointer> values) {
|
|
if (!logging_enabled(ctx)) {
|
|
return;
|
|
}
|
|
Span<uint64_t> keys = preview_sockets.lookup(socket);
|
|
if (!keys.is_empty()) {
|
|
log_preview_socket_value(values, ctx->object, keys);
|
|
}
|
|
log_ui_hints(socket, values, ctx->object, nmd);
|
|
};
|
|
|
|
blender::modifiers::geometry_nodes::GeometryNodesEvaluationParams eval_params;
|
|
eval_params.input_values = group_inputs;
|
|
eval_params.output_sockets = group_outputs;
|
|
eval_params.mf_by_node = &mf_by_node;
|
|
eval_params.modifier_ = nmd;
|
|
eval_params.depsgraph = ctx->depsgraph;
|
|
eval_params.self_object = ctx->object;
|
|
eval_params.log_socket_value_fn = log_socket_value;
|
|
blender::modifiers::geometry_nodes::evaluate_geometry_nodes(eval_params);
|
|
|
|
BLI_assert(eval_params.r_output_values.size() == 1);
|
|
GMutablePointer result = eval_params.r_output_values[0];
|
|
return result.relocate_out<GeometrySet>();
|
|
}
|
|
|
|
/**
|
|
* \note This could be done in #initialize_group_input, though that would require adding the
|
|
* the object as a parameter, so it's likely better to this check as a separate step.
|
|
*/
|
|
static void check_property_socket_sync(const Object *ob, ModifierData *md)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
|
|
int i = 0;
|
|
LISTBASE_FOREACH_INDEX (const bNodeSocket *, socket, &nmd->node_group->inputs, i) {
|
|
/* The first socket is the special geometry socket for the modifier object. */
|
|
if (i == 0 && socket->type == SOCK_GEOMETRY) {
|
|
continue;
|
|
}
|
|
|
|
IDProperty *property = IDP_GetPropertyFromGroup(nmd->settings.properties, socket->identifier);
|
|
if (property == nullptr) {
|
|
if (socket->type == SOCK_GEOMETRY) {
|
|
BKE_modifier_set_error(ob, md, "Node group can only have one geometry input");
|
|
}
|
|
else {
|
|
BKE_modifier_set_error(ob, md, "Missing property for input socket \"%s\"", socket->name);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const SocketPropertyType *property_type = get_socket_property_type(*socket);
|
|
if (!property_type->is_correct_type(*property)) {
|
|
BKE_modifier_set_error(
|
|
ob, md, "Property type does not match input socket \"(%s)\"", socket->name);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bool has_geometry_output = false;
|
|
LISTBASE_FOREACH (const bNodeSocket *, socket, &nmd->node_group->outputs) {
|
|
if (socket->type == SOCK_GEOMETRY) {
|
|
has_geometry_output = true;
|
|
}
|
|
}
|
|
|
|
if (!has_geometry_output) {
|
|
BKE_modifier_set_error(ob, md, "Node group must have a geometry output");
|
|
}
|
|
}
|
|
|
|
static void modifyGeometry(ModifierData *md,
|
|
const ModifierEvalContext *ctx,
|
|
GeometrySet &geometry_set)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
if (nmd->node_group == nullptr) {
|
|
return;
|
|
}
|
|
|
|
check_property_socket_sync(ctx->object, md);
|
|
|
|
NodeTreeRefMap tree_refs;
|
|
DerivedNodeTree tree{*nmd->node_group, tree_refs};
|
|
|
|
if (tree.has_link_cycles()) {
|
|
BKE_modifier_set_error(ctx->object, md, "Node group has cycles");
|
|
return;
|
|
}
|
|
|
|
const NodeTreeRef &root_tree_ref = tree.root_context().tree();
|
|
Span<const NodeRef *> input_nodes = root_tree_ref.nodes_by_type("NodeGroupInput");
|
|
Span<const NodeRef *> output_nodes = root_tree_ref.nodes_by_type("NodeGroupOutput");
|
|
|
|
if (output_nodes.size() != 1) {
|
|
return;
|
|
}
|
|
|
|
Span<const InputSocketRef *> group_outputs = output_nodes[0]->inputs().drop_back(1);
|
|
|
|
if (group_outputs.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
const InputSocketRef *group_output = group_outputs[0];
|
|
if (group_output->idname() != "NodeSocketGeometry") {
|
|
return;
|
|
}
|
|
|
|
if (logging_enabled(ctx)) {
|
|
reset_tree_ui_storage(tree.used_node_tree_refs(), *ctx->object, *md);
|
|
}
|
|
|
|
geometry_set = compute_geometry(
|
|
tree, input_nodes, *group_outputs[0], std::move(geometry_set), nmd, ctx);
|
|
}
|
|
|
|
static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh)
|
|
{
|
|
GeometrySet geometry_set = GeometrySet::create_with_mesh(mesh, GeometryOwnershipType::Editable);
|
|
geometry_set.get_component_for_write<MeshComponent>().copy_vertex_group_names_from_object(
|
|
*ctx->object);
|
|
modifyGeometry(md, ctx, geometry_set);
|
|
|
|
/* This function is only called when applying modifiers. In this case it makes sense to realize
|
|
* instances, otherwise in some cases there might be no results when applying the modifier. */
|
|
geometry_set = blender::bke::geometry_set_realize_mesh_for_modifier(geometry_set);
|
|
|
|
Mesh *new_mesh = geometry_set.get_component_for_write<MeshComponent>().release();
|
|
if (new_mesh == nullptr) {
|
|
return BKE_mesh_new_nomain(0, 0, 0, 0, 0);
|
|
}
|
|
return new_mesh;
|
|
}
|
|
|
|
static void modifyGeometrySet(ModifierData *md,
|
|
const ModifierEvalContext *ctx,
|
|
GeometrySet *geometry_set)
|
|
{
|
|
modifyGeometry(md, ctx, *geometry_set);
|
|
}
|
|
|
|
/* Drawing the properties manually with #uiItemR instead of #uiDefAutoButsRNA allows using
|
|
* the node socket identifier for the property names, since they are unique, but also having
|
|
* the correct label displayed in the UI. */
|
|
static void draw_property_for_socket(uiLayout *layout,
|
|
PointerRNA *bmain_ptr,
|
|
PointerRNA *md_ptr,
|
|
const IDProperty *modifier_props,
|
|
const bNodeSocket &socket)
|
|
{
|
|
const SocketPropertyType *property_type = get_socket_property_type(socket);
|
|
if (property_type == nullptr) {
|
|
return;
|
|
}
|
|
|
|
/* The property should be created in #MOD_nodes_update_interface with the correct type. */
|
|
IDProperty *property = IDP_GetPropertyFromGroup(modifier_props, socket.identifier);
|
|
|
|
/* IDProperties can be removed with python, so there could be a situation where
|
|
* there isn't a property for a socket or it doesn't have the correct type. */
|
|
if (property == nullptr || !property_type->is_correct_type(*property)) {
|
|
return;
|
|
}
|
|
|
|
char socket_id_esc[sizeof(socket.identifier) * 2];
|
|
BLI_str_escape(socket_id_esc, socket.identifier, sizeof(socket_id_esc));
|
|
|
|
char rna_path[sizeof(socket_id_esc) + 4];
|
|
BLI_snprintf(rna_path, ARRAY_SIZE(rna_path), "[\"%s\"]", socket_id_esc);
|
|
|
|
/* Use #uiItemPointerR to draw pointer properties because #uiItemR would not have enough
|
|
* information about what type of ID to select for editing the values. This is because
|
|
* pointer IDProperties contain no information about their type. */
|
|
switch (socket.type) {
|
|
case SOCK_OBJECT: {
|
|
uiItemPointerR(
|
|
layout, md_ptr, rna_path, bmain_ptr, "objects", socket.name, ICON_OBJECT_DATA);
|
|
break;
|
|
}
|
|
case SOCK_COLLECTION: {
|
|
uiItemPointerR(layout,
|
|
md_ptr,
|
|
rna_path,
|
|
bmain_ptr,
|
|
"collections",
|
|
socket.name,
|
|
ICON_OUTLINER_COLLECTION);
|
|
break;
|
|
}
|
|
case SOCK_MATERIAL: {
|
|
uiItemPointerR(layout, md_ptr, rna_path, bmain_ptr, "materials", socket.name, ICON_MATERIAL);
|
|
break;
|
|
}
|
|
case SOCK_TEXTURE: {
|
|
uiItemPointerR(layout, md_ptr, rna_path, bmain_ptr, "textures", socket.name, ICON_TEXTURE);
|
|
break;
|
|
}
|
|
default:
|
|
uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE);
|
|
}
|
|
}
|
|
|
|
static void panel_draw(const bContext *C, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
Main *bmain = CTX_data_main(C);
|
|
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
|
|
NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data);
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
uiLayoutSetPropDecorate(layout, true);
|
|
|
|
uiTemplateID(layout,
|
|
C,
|
|
ptr,
|
|
"node_group",
|
|
"node.new_geometry_node_group_assign",
|
|
nullptr,
|
|
nullptr,
|
|
0,
|
|
false,
|
|
nullptr);
|
|
|
|
if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) {
|
|
PointerRNA bmain_ptr;
|
|
RNA_main_pointer_create(bmain, &bmain_ptr);
|
|
|
|
LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) {
|
|
draw_property_for_socket(layout, &bmain_ptr, ptr, nmd->settings.properties, *socket);
|
|
}
|
|
}
|
|
|
|
modifier_panel_end(layout, ptr);
|
|
}
|
|
|
|
static void panelRegister(ARegionType *region_type)
|
|
{
|
|
modifier_panel_register(region_type, eModifierType_Nodes, panel_draw);
|
|
}
|
|
|
|
static void blendWrite(BlendWriter *writer, const ModifierData *md)
|
|
{
|
|
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
|
|
if (nmd->settings.properties != nullptr) {
|
|
/* Note that the property settings are based on the socket type info
|
|
* and don't necessarily need to be written, but we can't just free them. */
|
|
IDP_BlendWrite(writer, nmd->settings.properties);
|
|
}
|
|
}
|
|
|
|
static void blendRead(BlendDataReader *reader, ModifierData *md)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
BLO_read_data_address(reader, &nmd->settings.properties);
|
|
IDP_BlendDataRead(reader, &nmd->settings.properties);
|
|
}
|
|
|
|
static void copyData(const ModifierData *md, ModifierData *target, const int flag)
|
|
{
|
|
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
|
|
NodesModifierData *tnmd = reinterpret_cast<NodesModifierData *>(target);
|
|
|
|
BKE_modifier_copydata_generic(md, target, flag);
|
|
|
|
if (nmd->settings.properties != nullptr) {
|
|
tnmd->settings.properties = IDP_CopyProperty_ex(nmd->settings.properties, flag);
|
|
}
|
|
}
|
|
|
|
static void freeData(ModifierData *md)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
if (nmd->settings.properties != nullptr) {
|
|
IDP_FreeProperty_ex(nmd->settings.properties, false);
|
|
nmd->settings.properties = nullptr;
|
|
}
|
|
}
|
|
|
|
static void requiredDataMask(Object *UNUSED(ob),
|
|
ModifierData *UNUSED(md),
|
|
CustomData_MeshMasks *r_cddata_masks)
|
|
{
|
|
/* We don't know what the node tree will need. If there are vertex groups, it is likely that the
|
|
* node tree wants to access them. */
|
|
r_cddata_masks->vmask |= CD_MASK_MDEFORMVERT;
|
|
r_cddata_masks->vmask |= CD_MASK_PROP_ALL;
|
|
}
|
|
|
|
ModifierTypeInfo modifierType_Nodes = {
|
|
/* name */ "GeometryNodes",
|
|
/* structName */ "NodesModifierData",
|
|
/* structSize */ sizeof(NodesModifierData),
|
|
/* srna */ &RNA_NodesModifier,
|
|
/* type */ eModifierTypeType_Constructive,
|
|
/* flags */
|
|
static_cast<ModifierTypeFlag>(
|
|
eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode |
|
|
eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping),
|
|
/* icon */ ICON_NODETREE,
|
|
|
|
/* copyData */ copyData,
|
|
|
|
/* deformVerts */ nullptr,
|
|
/* deformMatrices */ nullptr,
|
|
/* deformVertsEM */ nullptr,
|
|
/* deformMatricesEM */ nullptr,
|
|
/* modifyMesh */ modifyMesh,
|
|
/* modifyHair */ nullptr,
|
|
/* modifyGeometrySet */ modifyGeometrySet,
|
|
|
|
/* initData */ initData,
|
|
/* requiredDataMask */ requiredDataMask,
|
|
/* freeData */ freeData,
|
|
/* isDisabled */ isDisabled,
|
|
/* updateDepsgraph */ updateDepsgraph,
|
|
/* dependsOnTime */ nullptr,
|
|
/* dependsOnNormals */ nullptr,
|
|
/* foreachIDLink */ foreachIDLink,
|
|
/* foreachTexLink */ foreachTexLink,
|
|
/* freeRuntimeData */ nullptr,
|
|
/* panelRegister */ panelRegister,
|
|
/* blendWrite */ blendWrite,
|
|
/* blendRead */ blendRead,
|
|
};
|