This adds a new "Active Camera" input geometry node, per #105761. The node outputs the the scene's current active camera. It is available from Input > Scene > Active Camera in the geometry nodes Add menu. Typical usage would be to connect this node to an Object Info node to obtain its transform. This works as expected when the camera's transform is animated, and also when there are markers on the timeline that change the active camera. In order to support the aforementioned changes in the active camera, this implementation adds depsgraph relations for all cameras referenced by timeline markers. This eliminates the complexity of updating the depsgraph whenever the scene switches to a different active camera, but of course it comes at the cost of including more objects than strictly necessary in the depsgraph for scenes that switch cameras. Dynamically updating the depsgraph upon camera changes could be a future improvement if there proves to be sufficient need for it. Pull Request: https://projects.blender.org/blender/blender/pulls/113431
1930 lines
70 KiB
C++
1930 lines
70 KiB
C++
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup modifiers
|
|
*/
|
|
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_array.hh"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math_vector_types.hh"
|
|
#include "BLI_multi_value_map.hh"
|
|
#include "BLI_path_util.h"
|
|
#include "BLI_set.hh"
|
|
#include "BLI_string.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DNA_collection_types.h"
|
|
#include "DNA_curves_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_view3d_types.h"
|
|
#include "DNA_windowmanager_types.h"
|
|
|
|
#include "BKE_attribute_math.hh"
|
|
#include "BKE_bake_geometry_nodes_modifier.hh"
|
|
#include "BKE_compute_contexts.hh"
|
|
#include "BKE_customdata.hh"
|
|
#include "BKE_geometry_fields.hh"
|
|
#include "BKE_geometry_set_instances.hh"
|
|
#include "BKE_global.h"
|
|
#include "BKE_idprop.hh"
|
|
#include "BKE_lib_id.h"
|
|
#include "BKE_lib_query.h"
|
|
#include "BKE_main.hh"
|
|
#include "BKE_mesh.hh"
|
|
#include "BKE_modifier.hh"
|
|
#include "BKE_node_runtime.hh"
|
|
#include "BKE_node_tree_update.hh"
|
|
#include "BKE_object.hh"
|
|
#include "BKE_pointcloud.h"
|
|
#include "BKE_screen.hh"
|
|
#include "BKE_workspace.h"
|
|
|
|
#include "BLO_read_write.hh"
|
|
|
|
#include "UI_interface.hh"
|
|
#include "UI_resources.hh"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "WM_types.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_enum_types.hh"
|
|
#include "RNA_prototypes.h"
|
|
|
|
#include "DEG_depsgraph_build.hh"
|
|
#include "DEG_depsgraph_query.hh"
|
|
|
|
#include "MOD_modifiertypes.hh"
|
|
#include "MOD_nodes.hh"
|
|
#include "MOD_ui_common.hh"
|
|
|
|
#include "ED_object.hh"
|
|
#include "ED_screen.hh"
|
|
#include "ED_spreadsheet.hh"
|
|
#include "ED_undo.hh"
|
|
#include "ED_viewer_path.hh"
|
|
|
|
#include "NOD_geometry.hh"
|
|
#include "NOD_geometry_nodes_execute.hh"
|
|
#include "NOD_geometry_nodes_lazy_function.hh"
|
|
#include "NOD_node_declaration.hh"
|
|
|
|
#include "FN_field.hh"
|
|
#include "FN_lazy_function_execute.hh"
|
|
#include "FN_lazy_function_graph_executor.hh"
|
|
#include "FN_multi_function.hh"
|
|
|
|
namespace lf = blender::fn::lazy_function;
|
|
namespace geo_log = blender::nodes::geo_eval_log;
|
|
namespace bake = blender::bke::bake;
|
|
|
|
namespace blender {
|
|
|
|
static void init_data(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);
|
|
nmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
|
|
nmd->runtime->cache = std::make_shared<bake::ModifierCache>();
|
|
}
|
|
|
|
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 update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
if (nmd->node_group == nullptr) {
|
|
return;
|
|
}
|
|
|
|
DEG_add_node_tree_output_relation(ctx->node, nmd->node_group, "Nodes Modifier");
|
|
|
|
bool needs_own_transform_relation = false;
|
|
bool needs_scene_camera_relation = false;
|
|
Set<ID *> used_ids;
|
|
find_used_ids_from_settings(nmd->settings, used_ids);
|
|
nodes::find_node_tree_dependencies(
|
|
*nmd->node_group, used_ids, needs_own_transform_relation, needs_scene_camera_relation);
|
|
|
|
if (ctx->object->type == OB_CURVES) {
|
|
Curves *curves_id = static_cast<Curves *>(ctx->object->data);
|
|
if (curves_id->surface != nullptr) {
|
|
used_ids.add(&curves_id->surface->id);
|
|
}
|
|
}
|
|
|
|
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_IM:
|
|
case ID_TE: {
|
|
DEG_add_generic_id_relation(ctx->node, id, "Nodes Modifier");
|
|
break;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needs_own_transform_relation) {
|
|
DEG_add_depends_on_transform_relation(ctx->node, "Nodes Modifier");
|
|
}
|
|
if (needs_scene_camera_relation) {
|
|
DEG_add_scene_camera_relation(ctx->node, ctx->scene, DEG_OB_COMP_TRANSFORM, "Nodes Modifier");
|
|
/* Active camera is a scene parameter that can change, so we need a relation for that, too. */
|
|
DEG_add_scene_relation(ctx->node, ctx->scene, DEG_SCENE_COMP_PARAMETERS, "Nodes Modifier");
|
|
}
|
|
}
|
|
|
|
static bool check_tree_for_time_node(const bNodeTree &tree, Set<const bNodeTree *> &checked_groups)
|
|
{
|
|
if (!checked_groups.add(&tree)) {
|
|
return false;
|
|
}
|
|
tree.ensure_topology_cache();
|
|
if (!tree.nodes_by_type("GeometryNodeInputSceneTime").is_empty()) {
|
|
return true;
|
|
}
|
|
if (!tree.nodes_by_type("GeometryNodeSimulationInput").is_empty()) {
|
|
return true;
|
|
}
|
|
for (const bNode *node : tree.group_nodes()) {
|
|
if (const bNodeTree *sub_tree = reinterpret_cast<const bNodeTree *>(node->id)) {
|
|
if (check_tree_for_time_node(*sub_tree, checked_groups)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool depends_on_time(Scene * /*scene*/, ModifierData *md)
|
|
{
|
|
const NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
const bNodeTree *tree = nmd->node_group;
|
|
if (tree == nullptr) {
|
|
return false;
|
|
}
|
|
Set<const bNodeTree *> checked_groups;
|
|
return check_tree_for_time_node(*tree, checked_groups);
|
|
}
|
|
|
|
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
walk(user_data, ob, (ID **)&nmd->node_group, IDWALK_CB_USER);
|
|
|
|
struct ForeachSettingData {
|
|
IDWalkFunc walk;
|
|
void *user_data;
|
|
Object *ob;
|
|
} settings = {walk, user_data, 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->user_data, settings->ob, (ID **)&id_prop->data.pointer, IDWALK_CB_USER);
|
|
},
|
|
&settings);
|
|
}
|
|
|
|
static void foreach_tex_link(ModifierData *md, Object *ob, TexWalkFunc walk, void *user_data)
|
|
{
|
|
walk(user_data, ob, md, "texture");
|
|
}
|
|
|
|
static bool is_disabled(const Scene * /*scene*/, ModifierData *md, bool /*use_render_params*/)
|
|
{
|
|
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;
|
|
}
|
|
|
|
static void update_id_properties_from_node_group(NodesModifierData *nmd)
|
|
{
|
|
if (nmd->node_group == nullptr) {
|
|
if (nmd->settings.properties) {
|
|
IDP_FreeProperty(nmd->settings.properties);
|
|
nmd->settings.properties = nullptr;
|
|
}
|
|
return;
|
|
}
|
|
|
|
IDProperty *old_properties = nmd->settings.properties;
|
|
{
|
|
IDPropertyTemplate idprop = {0};
|
|
nmd->settings.properties = IDP_New(IDP_GROUP, &idprop, "Nodes Modifier Settings");
|
|
}
|
|
IDProperty *new_properties = nmd->settings.properties;
|
|
|
|
nodes::update_input_properties_from_node_tree(
|
|
*nmd->node_group, old_properties, false, *new_properties);
|
|
nodes::update_output_properties_from_node_tree(
|
|
*nmd->node_group, old_properties, *new_properties);
|
|
|
|
if (old_properties != nullptr) {
|
|
IDP_FreeProperty(old_properties);
|
|
}
|
|
}
|
|
|
|
static void update_existing_bake_caches(NodesModifierData &nmd)
|
|
{
|
|
if (!nmd.runtime->cache) {
|
|
if (nmd.bakes_num == 0) {
|
|
return;
|
|
}
|
|
nmd.runtime->cache = std::make_shared<bake::ModifierCache>();
|
|
}
|
|
bake::ModifierCache &modifier_cache = *nmd.runtime->cache;
|
|
std::lock_guard lock{modifier_cache.mutex};
|
|
|
|
Map<int, std::unique_ptr<bake::SimulationNodeCache>> &old_cache_by_id =
|
|
modifier_cache.simulation_cache_by_id;
|
|
Map<int, std::unique_ptr<bake::SimulationNodeCache>> new_cache_by_id;
|
|
for (const NodesModifierBake &bake : Span{nmd.bakes, nmd.bakes_num}) {
|
|
std::unique_ptr<bake::SimulationNodeCache> node_cache;
|
|
std::unique_ptr<bake::SimulationNodeCache> *old_node_cache_ptr = old_cache_by_id.lookup_ptr(
|
|
bake.id);
|
|
if (old_node_cache_ptr == nullptr) {
|
|
node_cache = std::make_unique<bake::SimulationNodeCache>();
|
|
}
|
|
else {
|
|
node_cache = std::move(*old_node_cache_ptr);
|
|
}
|
|
new_cache_by_id.add(bake.id, std::move(node_cache));
|
|
}
|
|
modifier_cache.simulation_cache_by_id = std::move(new_cache_by_id);
|
|
}
|
|
|
|
static void update_bakes_from_node_group(NodesModifierData &nmd)
|
|
{
|
|
Map<int, NodesModifierBake *> old_bake_by_id;
|
|
for (NodesModifierBake &bake : MutableSpan(nmd.bakes, nmd.bakes_num)) {
|
|
old_bake_by_id.add(bake.id, &bake);
|
|
}
|
|
|
|
Vector<int> new_bake_ids;
|
|
if (nmd.node_group) {
|
|
for (const bNestedNodeRef &ref : nmd.node_group->nested_node_refs_span()) {
|
|
const bNode *node = nmd.node_group->find_nested_node(ref.id);
|
|
if (node) {
|
|
if (node->type == GEO_NODE_SIMULATION_OUTPUT) {
|
|
new_bake_ids.append(ref.id);
|
|
}
|
|
}
|
|
else if (old_bake_by_id.contains(ref.id)) {
|
|
/* Keep baked data in case linked data is missing so that it still exists when the linked
|
|
* data has been found. */
|
|
new_bake_ids.append(ref.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
NodesModifierBake *new_bake_data = MEM_cnew_array<NodesModifierBake>(new_bake_ids.size(),
|
|
__func__);
|
|
for (const int i : new_bake_ids.index_range()) {
|
|
const int id = new_bake_ids[i];
|
|
NodesModifierBake *old_bake = old_bake_by_id.lookup_default(id, nullptr);
|
|
NodesModifierBake &new_bake = new_bake_data[i];
|
|
if (old_bake) {
|
|
new_bake = *old_bake;
|
|
/* The ownership of the string was moved to `new_bake`. */
|
|
old_bake->directory = nullptr;
|
|
}
|
|
else {
|
|
new_bake.id = id;
|
|
new_bake.frame_start = 1;
|
|
new_bake.frame_end = 100;
|
|
}
|
|
}
|
|
|
|
for (NodesModifierBake &old_bake : MutableSpan(nmd.bakes, nmd.bakes_num)) {
|
|
MEM_SAFE_FREE(old_bake.directory);
|
|
}
|
|
MEM_SAFE_FREE(nmd.bakes);
|
|
|
|
nmd.bakes = new_bake_data;
|
|
nmd.bakes_num = new_bake_ids.size();
|
|
|
|
update_existing_bake_caches(nmd);
|
|
}
|
|
|
|
} // namespace blender
|
|
|
|
void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
|
|
{
|
|
using namespace blender;
|
|
update_id_properties_from_node_group(nmd);
|
|
update_bakes_from_node_group(*nmd);
|
|
|
|
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
|
}
|
|
|
|
NodesModifierBake *NodesModifierData::find_bake(const int id)
|
|
{
|
|
return const_cast<NodesModifierBake *>(std::as_const(*this).find_bake(id));
|
|
}
|
|
|
|
const NodesModifierBake *NodesModifierData::find_bake(const int id) const
|
|
{
|
|
for (const NodesModifierBake &bake : blender::Span{this->bakes, this->bakes_num}) {
|
|
if (bake.id == id) {
|
|
return &bake;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
namespace blender {
|
|
|
|
/**
|
|
* Setup side effects nodes so that the given node in the given compute context will be executed.
|
|
* To make sure that it is executed, all parent group nodes and zones have to be set to have side
|
|
* effects as well.
|
|
*/
|
|
static void try_add_side_effect_node(const ComputeContext &final_compute_context,
|
|
const int final_node_id,
|
|
const NodesModifierData &nmd,
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
|
|
{
|
|
if (nmd.node_group == nullptr) {
|
|
return;
|
|
}
|
|
|
|
Vector<const ComputeContext *> compute_context_vec;
|
|
for (const ComputeContext *c = &final_compute_context; c; c = c->parent()) {
|
|
compute_context_vec.append(c);
|
|
}
|
|
std::reverse(compute_context_vec.begin(), compute_context_vec.end());
|
|
|
|
const auto *modifier_compute_context = dynamic_cast<const bke::ModifierComputeContext *>(
|
|
compute_context_vec[0]);
|
|
if (modifier_compute_context == nullptr) {
|
|
return;
|
|
}
|
|
if (modifier_compute_context->modifier_name() != nmd.modifier.name) {
|
|
return;
|
|
}
|
|
|
|
const bNodeTree *current_tree = nmd.node_group;
|
|
const bke::bNodeTreeZone *current_zone = nullptr;
|
|
|
|
/* Write side effect nodes to a new map and only if everything succeeds, move the nodes to the
|
|
* caller. This is easier than changing r_side_effect_nodes directly and then undoing changes in
|
|
* case of errors. */
|
|
nodes::GeoNodesSideEffectNodes local_side_effect_nodes;
|
|
for (const ComputeContext *compute_context_generic : compute_context_vec.as_span().drop_front(1))
|
|
{
|
|
const bke::bNodeTreeZones *current_zones = current_tree->zones();
|
|
if (current_zones == nullptr) {
|
|
return;
|
|
}
|
|
const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*current_tree);
|
|
if (lf_graph_info == nullptr) {
|
|
return;
|
|
}
|
|
const ComputeContextHash &parent_compute_context_hash =
|
|
compute_context_generic->parent()->hash();
|
|
if (const auto *compute_context = dynamic_cast<const bke::SimulationZoneComputeContext *>(
|
|
compute_context_generic))
|
|
{
|
|
const bke::bNodeTreeZone *simulation_zone = current_zones->get_zone_by_node(
|
|
compute_context->output_node_id());
|
|
if (simulation_zone == nullptr) {
|
|
return;
|
|
}
|
|
if (simulation_zone->parent_zone != current_zone) {
|
|
return;
|
|
}
|
|
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
|
|
simulation_zone, nullptr);
|
|
if (lf_zone_node == nullptr) {
|
|
return;
|
|
}
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
|
|
current_zone = simulation_zone;
|
|
}
|
|
else if (const auto *compute_context = dynamic_cast<const bke::RepeatZoneComputeContext *>(
|
|
compute_context_generic))
|
|
{
|
|
const bke::bNodeTreeZone *repeat_zone = current_zones->get_zone_by_node(
|
|
compute_context->output_node_id());
|
|
if (repeat_zone == nullptr) {
|
|
return;
|
|
}
|
|
if (repeat_zone->parent_zone != current_zone) {
|
|
return;
|
|
}
|
|
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
|
|
repeat_zone, nullptr);
|
|
if (lf_zone_node == nullptr) {
|
|
return;
|
|
}
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
|
|
local_side_effect_nodes.iterations_by_repeat_zone.add(
|
|
{parent_compute_context_hash, compute_context->output_node_id()},
|
|
compute_context->iteration());
|
|
current_zone = repeat_zone;
|
|
}
|
|
else if (const auto *compute_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
|
|
compute_context_generic))
|
|
{
|
|
const bNode *group_node = current_tree->node_by_id(compute_context->node_id());
|
|
if (group_node == nullptr) {
|
|
return;
|
|
}
|
|
if (group_node->id == nullptr) {
|
|
return;
|
|
}
|
|
if (group_node->is_muted()) {
|
|
return;
|
|
}
|
|
if (current_zone != current_zones->get_zone_by_node(group_node->identifier)) {
|
|
return;
|
|
}
|
|
const lf::FunctionNode *lf_group_node = lf_graph_info->mapping.group_node_map.lookup_default(
|
|
group_node, nullptr);
|
|
if (lf_group_node == nullptr) {
|
|
return;
|
|
}
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_group_node);
|
|
current_tree = reinterpret_cast<const bNodeTree *>(group_node->id);
|
|
current_zone = nullptr;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
const bNode *final_node = current_tree->node_by_id(final_node_id);
|
|
if (final_node == nullptr) {
|
|
return;
|
|
}
|
|
const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*current_tree);
|
|
if (lf_graph_info == nullptr) {
|
|
return;
|
|
}
|
|
const bke::bNodeTreeZones *tree_zones = current_tree->zones();
|
|
if (tree_zones == nullptr) {
|
|
return;
|
|
}
|
|
if (tree_zones->get_zone_by_node(final_node_id) != current_zone) {
|
|
return;
|
|
}
|
|
const lf::FunctionNode *lf_node =
|
|
lf_graph_info->mapping.possible_side_effect_node_map.lookup_default(final_node, nullptr);
|
|
if (lf_node == nullptr) {
|
|
return;
|
|
}
|
|
local_side_effect_nodes.nodes_by_context.add(final_compute_context.hash(), lf_node);
|
|
|
|
/* Successfully found all side effect nodes for the viewer path. */
|
|
for (const auto item : local_side_effect_nodes.nodes_by_context.items()) {
|
|
r_side_effect_nodes.nodes_by_context.add_multiple(item.key, item.value);
|
|
}
|
|
for (const auto item : local_side_effect_nodes.iterations_by_repeat_zone.items()) {
|
|
r_side_effect_nodes.iterations_by_repeat_zone.add_multiple(item.key, item.value);
|
|
}
|
|
}
|
|
|
|
static void find_side_effect_nodes_for_viewer_path(
|
|
const ViewerPath &viewer_path,
|
|
const NodesModifierData &nmd,
|
|
const ModifierEvalContext &ctx,
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
|
|
{
|
|
const std::optional<ed::viewer_path::ViewerPathForGeometryNodesViewer> parsed_path =
|
|
ed::viewer_path::parse_geometry_nodes_viewer(viewer_path);
|
|
if (!parsed_path.has_value()) {
|
|
return;
|
|
}
|
|
if (parsed_path->object != DEG_get_original_object(ctx.object)) {
|
|
return;
|
|
}
|
|
if (parsed_path->modifier_name != nmd.modifier.name) {
|
|
return;
|
|
}
|
|
|
|
ComputeContextBuilder compute_context_builder;
|
|
compute_context_builder.push<bke::ModifierComputeContext>(parsed_path->modifier_name);
|
|
|
|
for (const ViewerPathElem *elem : parsed_path->node_path) {
|
|
if (!ed::viewer_path::add_compute_context_for_viewer_path_elem(*elem, compute_context_builder))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
try_add_side_effect_node(
|
|
*compute_context_builder.current(), parsed_path->viewer_node_id, nmd, r_side_effect_nodes);
|
|
}
|
|
|
|
static void find_side_effect_nodes(const NodesModifierData &nmd,
|
|
const ModifierEvalContext &ctx,
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
|
|
{
|
|
Main *bmain = DEG_get_bmain(ctx.depsgraph);
|
|
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
|
|
if (wm == nullptr) {
|
|
return;
|
|
}
|
|
LISTBASE_FOREACH (const wmWindow *, window, &wm->windows) {
|
|
const bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
|
|
const WorkSpace *workspace = BKE_workspace_active_get(window->workspace_hook);
|
|
find_side_effect_nodes_for_viewer_path(workspace->viewer_path, nmd, ctx, r_side_effect_nodes);
|
|
LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {
|
|
const SpaceLink *sl = static_cast<SpaceLink *>(area->spacedata.first);
|
|
if (sl->spacetype == SPACE_SPREADSHEET) {
|
|
const SpaceSpreadsheet &sspreadsheet = *reinterpret_cast<const SpaceSpreadsheet *>(sl);
|
|
find_side_effect_nodes_for_viewer_path(
|
|
sspreadsheet.viewer_path, nmd, ctx, r_side_effect_nodes);
|
|
}
|
|
if (sl->spacetype == SPACE_VIEW3D) {
|
|
const View3D &v3d = *reinterpret_cast<const View3D *>(sl);
|
|
find_side_effect_nodes_for_viewer_path(v3d.viewer_path, nmd, ctx, r_side_effect_nodes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void find_socket_log_contexts(const NodesModifierData &nmd,
|
|
const ModifierEvalContext &ctx,
|
|
Set<ComputeContextHash> &r_socket_log_contexts)
|
|
{
|
|
Main *bmain = DEG_get_bmain(ctx.depsgraph);
|
|
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
|
|
if (wm == nullptr) {
|
|
return;
|
|
}
|
|
LISTBASE_FOREACH (const wmWindow *, window, &wm->windows) {
|
|
const bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
|
|
LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {
|
|
const SpaceLink *sl = static_cast<SpaceLink *>(area->spacedata.first);
|
|
if (sl->spacetype == SPACE_NODE) {
|
|
const SpaceNode &snode = *reinterpret_cast<const SpaceNode *>(sl);
|
|
if (snode.edittree == nullptr) {
|
|
continue;
|
|
}
|
|
const Map<const bke::bNodeTreeZone *, ComputeContextHash> hash_by_zone =
|
|
geo_log::GeoModifierLog::get_context_hash_by_zone_for_node_editor(snode,
|
|
nmd.modifier.name);
|
|
for (const ComputeContextHash &hash : hash_by_zone.values()) {
|
|
r_socket_log_contexts.add(hash);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \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 geometry_socket_count = 0;
|
|
|
|
nmd->node_group->ensure_interface_cache();
|
|
for (const int i : nmd->node_group->interface_inputs().index_range()) {
|
|
const bNodeTreeInterfaceSocket *socket = nmd->node_group->interface_inputs()[i];
|
|
const bNodeSocketType *typeinfo = socket->socket_typeinfo();
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
|
/* The first socket is the special geometry socket for the modifier object. */
|
|
if (i == 0 && type == SOCK_GEOMETRY) {
|
|
geometry_socket_count++;
|
|
continue;
|
|
}
|
|
|
|
IDProperty *property = IDP_GetPropertyFromGroup(nmd->settings.properties, socket->identifier);
|
|
if (property == nullptr) {
|
|
if (type == SOCK_GEOMETRY) {
|
|
geometry_socket_count++;
|
|
}
|
|
else {
|
|
BKE_modifier_set_error(
|
|
ob, md, "Missing property for input socket \"%s\"", socket->name ? socket->name : "");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!nodes::id_property_type_matches_socket(*socket, *property)) {
|
|
BKE_modifier_set_error(ob,
|
|
md,
|
|
"Property type does not match input socket \"(%s)\"",
|
|
socket->name ? socket->name : "");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (geometry_socket_count == 1) {
|
|
const bNodeTreeInterfaceSocket *first_socket = nmd->node_group->interface_inputs()[0];
|
|
const bNodeSocketType *typeinfo = first_socket->socket_typeinfo();
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
|
if (type != SOCK_GEOMETRY) {
|
|
BKE_modifier_set_error(ob, md, "Node group's geometry input must be the first");
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace sim_input = nodes::sim_input;
|
|
namespace sim_output = nodes::sim_output;
|
|
|
|
class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams {
|
|
private:
|
|
static constexpr float max_delta_frames = 1.0f;
|
|
|
|
mutable Map<int, std::unique_ptr<nodes::SimulationZoneBehavior>> behavior_by_zone_id_;
|
|
const NodesModifierData &nmd_;
|
|
const ModifierEvalContext &ctx_;
|
|
const Main *bmain_;
|
|
const Scene *scene_;
|
|
SubFrame current_frame_;
|
|
bool use_frame_cache_;
|
|
bool depsgraph_is_active_;
|
|
bake::ModifierCache *modifier_cache_;
|
|
float fps_;
|
|
bool has_invalid_simulation_ = false;
|
|
|
|
public:
|
|
NodesModifierSimulationParams(NodesModifierData &nmd, const ModifierEvalContext &ctx)
|
|
: nmd_(nmd), ctx_(ctx)
|
|
{
|
|
const Depsgraph *depsgraph = ctx_.depsgraph;
|
|
bmain_ = DEG_get_bmain(depsgraph);
|
|
current_frame_ = DEG_get_ctime(depsgraph);
|
|
const Scene *scene = DEG_get_input_scene(depsgraph);
|
|
scene_ = scene;
|
|
use_frame_cache_ = ctx_.object->flag & OB_FLAG_USE_SIMULATION_CACHE;
|
|
depsgraph_is_active_ = DEG_is_active(depsgraph);
|
|
modifier_cache_ = nmd.runtime->cache.get();
|
|
fps_ = FPS;
|
|
|
|
if (!modifier_cache_) {
|
|
return;
|
|
}
|
|
std::lock_guard lock{modifier_cache_->mutex};
|
|
if (depsgraph_is_active_) {
|
|
/* Invalidate data on user edits. */
|
|
if (nmd.modifier.flag & eModifierFlag_UserModified) {
|
|
for (std::unique_ptr<bake::SimulationNodeCache> &node_cache :
|
|
modifier_cache_->simulation_cache_by_id.values())
|
|
{
|
|
if (node_cache->cache_status != bake::CacheStatus::Baked) {
|
|
node_cache->cache_status = bake::CacheStatus::Invalid;
|
|
}
|
|
}
|
|
}
|
|
this->reset_invalid_node_bakes();
|
|
}
|
|
for (const std::unique_ptr<bake::SimulationNodeCache> &node_cache_ptr :
|
|
modifier_cache_->simulation_cache_by_id.values())
|
|
{
|
|
const bake::SimulationNodeCache &node_cache = *node_cache_ptr;
|
|
if (node_cache.cache_status == bake::CacheStatus::Invalid) {
|
|
has_invalid_simulation_ = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void reset_invalid_node_bakes()
|
|
{
|
|
for (auto item : modifier_cache_->simulation_cache_by_id.items()) {
|
|
const int id = item.key;
|
|
bake::SimulationNodeCache &node_cache = *item.value;
|
|
if (node_cache.cache_status != bake::CacheStatus::Invalid) {
|
|
continue;
|
|
}
|
|
const std::optional<IndexRange> sim_frame_range = bake::get_node_bake_frame_range(
|
|
*scene_, *ctx_.object, nmd_, id);
|
|
if (!sim_frame_range.has_value()) {
|
|
continue;
|
|
}
|
|
const SubFrame start_frame{int(sim_frame_range->start())};
|
|
if (current_frame_ <= start_frame) {
|
|
node_cache.reset();
|
|
}
|
|
if (!node_cache.frame_caches.is_empty() &&
|
|
current_frame_ < node_cache.frame_caches.first()->frame) {
|
|
node_cache.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
nodes::SimulationZoneBehavior *get(const int zone_id) const override
|
|
{
|
|
if (!modifier_cache_) {
|
|
return nullptr;
|
|
}
|
|
std::lock_guard lock{modifier_cache_->mutex};
|
|
return behavior_by_zone_id_
|
|
.lookup_or_add_cb(zone_id,
|
|
[&]() {
|
|
auto info = std::make_unique<nodes::SimulationZoneBehavior>();
|
|
this->init_simulation_info(zone_id, *info);
|
|
return info;
|
|
})
|
|
.get();
|
|
}
|
|
|
|
struct FrameIndices {
|
|
std::optional<int> prev;
|
|
std::optional<int> current;
|
|
std::optional<int> next;
|
|
};
|
|
|
|
void init_simulation_info(const int zone_id, nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
if (!modifier_cache_->simulation_cache_by_id.contains(zone_id)) {
|
|
/* Should have been created in #update_existing_bake_caches. */
|
|
return;
|
|
}
|
|
bake::SimulationNodeCache &node_cache = *modifier_cache_->simulation_cache_by_id.lookup(
|
|
zone_id);
|
|
const IndexRange sim_frame_range = *bake::get_node_bake_frame_range(
|
|
*scene_, *ctx_.object, nmd_, zone_id);
|
|
const SubFrame sim_start_frame{int(sim_frame_range.first())};
|
|
const SubFrame sim_end_frame{int(sim_frame_range.last())};
|
|
|
|
/* Try load baked data. */
|
|
if (!node_cache.failed_finding_bake) {
|
|
if (node_cache.cache_status != bake::CacheStatus::Baked) {
|
|
if (std::optional<bake::BakePath> zone_bake_path = bake::get_node_bake_path(
|
|
*bmain_, *ctx_.object, nmd_, zone_id))
|
|
{
|
|
|
|
Vector<bake::MetaFile> meta_files = bake::find_sorted_meta_files(
|
|
zone_bake_path->meta_dir);
|
|
if (!meta_files.is_empty()) {
|
|
node_cache.reset();
|
|
|
|
for (const bake::MetaFile &meta_file : meta_files) {
|
|
auto frame_cache = std::make_unique<bake::FrameCache>();
|
|
frame_cache->frame = meta_file.frame;
|
|
frame_cache->meta_path = meta_file.path;
|
|
node_cache.frame_caches.append(std::move(frame_cache));
|
|
}
|
|
node_cache.blobs_dir = zone_bake_path->blobs_dir;
|
|
node_cache.blob_sharing = std::make_unique<bke::bake::BlobSharing>();
|
|
node_cache.cache_status = bake::CacheStatus::Baked;
|
|
}
|
|
}
|
|
}
|
|
if (node_cache.cache_status != bake::CacheStatus::Baked) {
|
|
node_cache.failed_finding_bake = true;
|
|
}
|
|
}
|
|
|
|
const FrameIndices frame_indices = this->get_frame_indices(node_cache);
|
|
if (node_cache.cache_status == bake::CacheStatus::Baked) {
|
|
this->read_from_cache(frame_indices, node_cache, zone_behavior);
|
|
return;
|
|
}
|
|
if (use_frame_cache_) {
|
|
/* If the depsgraph is active, we allow creating new simulation states. Otherwise, the access
|
|
* is read-only. */
|
|
if (depsgraph_is_active_) {
|
|
if (node_cache.frame_caches.is_empty()) {
|
|
if (current_frame_ < sim_start_frame || current_frame_ > sim_end_frame) {
|
|
/* Outside of simulation frame range, so ignore the simulation if there is no cache. */
|
|
this->input_pass_through(zone_behavior);
|
|
this->output_pass_through(zone_behavior);
|
|
return;
|
|
}
|
|
/* Initialize the simulation. */
|
|
if (current_frame_ > sim_start_frame || has_invalid_simulation_) {
|
|
node_cache.cache_status = bake::CacheStatus::Invalid;
|
|
}
|
|
this->input_pass_through(zone_behavior);
|
|
this->output_store_frame_cache(node_cache, zone_behavior);
|
|
return;
|
|
}
|
|
if (frame_indices.prev && !frame_indices.current && !frame_indices.next &&
|
|
current_frame_ <= sim_end_frame)
|
|
{
|
|
/* Read the previous frame's data and store the newly computed simulation state. */
|
|
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
|
|
const bake::FrameCache &prev_frame_cache = *node_cache.frame_caches[*frame_indices.prev];
|
|
const float real_delta_frames = float(current_frame_) - float(prev_frame_cache.frame);
|
|
if (real_delta_frames != 1) {
|
|
node_cache.cache_status = bake::CacheStatus::Invalid;
|
|
}
|
|
const float delta_frames = std::min(max_delta_frames, real_delta_frames);
|
|
output_copy_info.delta_time = delta_frames / fps_;
|
|
output_copy_info.state = prev_frame_cache.state;
|
|
this->output_store_frame_cache(node_cache, zone_behavior);
|
|
return;
|
|
}
|
|
}
|
|
this->read_from_cache(frame_indices, node_cache, zone_behavior);
|
|
return;
|
|
}
|
|
|
|
/* When there is no per-frame cache, check if there is a previous state. */
|
|
if (node_cache.prev_cache) {
|
|
if (node_cache.prev_cache->frame < current_frame_) {
|
|
/* Do a simulation step. */
|
|
const float delta_frames = std::min(
|
|
max_delta_frames, float(current_frame_) - float(node_cache.prev_cache->frame));
|
|
auto &output_move_info = zone_behavior.input.emplace<sim_input::OutputMove>();
|
|
output_move_info.delta_time = delta_frames / fps_;
|
|
output_move_info.state = std::move(node_cache.prev_cache->state);
|
|
this->store_as_prev_items(node_cache, zone_behavior);
|
|
return;
|
|
}
|
|
if (node_cache.prev_cache->frame == current_frame_) {
|
|
/* Just read from the previous state if the frame has not changed. */
|
|
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
|
|
output_copy_info.delta_time = 0.0f;
|
|
output_copy_info.state = node_cache.prev_cache->state;
|
|
auto &read_single_info = zone_behavior.output.emplace<sim_output::ReadSingle>();
|
|
read_single_info.state = node_cache.prev_cache->state;
|
|
return;
|
|
}
|
|
if (!depsgraph_is_active_) {
|
|
/* There is no previous state, and it's not possible to initialize the simulation because
|
|
* the depsgraph is not active. */
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
zone_behavior.output.emplace<sim_output::PassThrough>();
|
|
return;
|
|
}
|
|
/* Reset the simulation when the scene time moved backwards. */
|
|
node_cache.prev_cache.reset();
|
|
}
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
if (depsgraph_is_active_) {
|
|
/* Initialize the simulation. */
|
|
this->store_as_prev_items(node_cache, zone_behavior);
|
|
}
|
|
else {
|
|
zone_behavior.output.emplace<sim_output::PassThrough>();
|
|
}
|
|
}
|
|
|
|
FrameIndices get_frame_indices(const bake::SimulationNodeCache &node_cache) const
|
|
{
|
|
FrameIndices frame_indices;
|
|
if (!node_cache.frame_caches.is_empty()) {
|
|
const int first_future_frame_index = binary_search::find_predicate_begin(
|
|
node_cache.frame_caches, [&](const std::unique_ptr<bake::FrameCache> &value) {
|
|
return value->frame > current_frame_;
|
|
});
|
|
frame_indices.next = (first_future_frame_index == node_cache.frame_caches.size()) ?
|
|
std::nullopt :
|
|
std::optional<int>(first_future_frame_index);
|
|
if (first_future_frame_index > 0) {
|
|
const int index = first_future_frame_index - 1;
|
|
if (node_cache.frame_caches[index]->frame < current_frame_) {
|
|
frame_indices.prev = index;
|
|
}
|
|
else {
|
|
BLI_assert(node_cache.frame_caches[index]->frame == current_frame_);
|
|
frame_indices.current = index;
|
|
if (index > 0) {
|
|
frame_indices.prev = index - 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return frame_indices;
|
|
}
|
|
|
|
void input_pass_through(nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
}
|
|
|
|
void output_pass_through(nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
zone_behavior.output.emplace<sim_output::PassThrough>();
|
|
}
|
|
|
|
void output_store_frame_cache(bake::SimulationNodeCache &node_cache,
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
auto &store_new_state_info = zone_behavior.output.emplace<sim_output::StoreNewState>();
|
|
store_new_state_info.store_fn = [simulation_cache = modifier_cache_,
|
|
node_cache = &node_cache,
|
|
current_frame = current_frame_](bke::bake::BakeState state) {
|
|
std::lock_guard lock{simulation_cache->mutex};
|
|
auto frame_cache = std::make_unique<bake::FrameCache>();
|
|
frame_cache->frame = current_frame;
|
|
frame_cache->state = std::move(state);
|
|
node_cache->frame_caches.append(std::move(frame_cache));
|
|
};
|
|
}
|
|
|
|
void store_as_prev_items(bake::SimulationNodeCache &node_cache,
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
auto &store_new_state_info = zone_behavior.output.emplace<sim_output::StoreNewState>();
|
|
store_new_state_info.store_fn = [simulation_cache = modifier_cache_,
|
|
node_cache = &node_cache,
|
|
current_frame = current_frame_](bke::bake::BakeState state) {
|
|
std::lock_guard lock{simulation_cache->mutex};
|
|
if (!node_cache->prev_cache) {
|
|
node_cache->prev_cache.emplace();
|
|
}
|
|
node_cache->prev_cache->state = std::move(state);
|
|
node_cache->prev_cache->frame = current_frame;
|
|
};
|
|
}
|
|
|
|
void read_from_cache(const FrameIndices &frame_indices,
|
|
bake::SimulationNodeCache &node_cache,
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
if (frame_indices.prev) {
|
|
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
|
|
bake::FrameCache &frame_cache = *node_cache.frame_caches[*frame_indices.prev];
|
|
const float delta_frames = std::min(max_delta_frames,
|
|
float(current_frame_) - float(frame_cache.frame));
|
|
output_copy_info.delta_time = delta_frames / fps_;
|
|
output_copy_info.state = frame_cache.state;
|
|
}
|
|
else {
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
}
|
|
if (frame_indices.current) {
|
|
this->read_single(*frame_indices.current, node_cache, zone_behavior);
|
|
}
|
|
else if (frame_indices.next) {
|
|
if (frame_indices.prev) {
|
|
this->read_interpolated(
|
|
*frame_indices.prev, *frame_indices.next, node_cache, zone_behavior);
|
|
}
|
|
else {
|
|
this->output_pass_through(zone_behavior);
|
|
}
|
|
}
|
|
else if (frame_indices.prev) {
|
|
this->read_single(*frame_indices.prev, node_cache, zone_behavior);
|
|
}
|
|
else {
|
|
this->output_pass_through(zone_behavior);
|
|
}
|
|
}
|
|
|
|
void read_single(const int frame_index,
|
|
bake::SimulationNodeCache &node_cache,
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
bake::FrameCache &frame_cache = *node_cache.frame_caches[frame_index];
|
|
this->ensure_bake_loaded(node_cache, frame_cache);
|
|
auto &read_single_info = zone_behavior.output.emplace<sim_output::ReadSingle>();
|
|
read_single_info.state = frame_cache.state;
|
|
}
|
|
|
|
void read_interpolated(const int prev_frame_index,
|
|
const int next_frame_index,
|
|
bake::SimulationNodeCache &node_cache,
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
bake::FrameCache &prev_frame_cache = *node_cache.frame_caches[prev_frame_index];
|
|
bake::FrameCache &next_frame_cache = *node_cache.frame_caches[next_frame_index];
|
|
this->ensure_bake_loaded(node_cache, prev_frame_cache);
|
|
this->ensure_bake_loaded(node_cache, next_frame_cache);
|
|
auto &read_interpolated_info = zone_behavior.output.emplace<sim_output::ReadInterpolated>();
|
|
read_interpolated_info.mix_factor = (float(current_frame_) - float(prev_frame_cache.frame)) /
|
|
(float(next_frame_cache.frame) -
|
|
float(prev_frame_cache.frame));
|
|
read_interpolated_info.prev_state = prev_frame_cache.state;
|
|
read_interpolated_info.next_state = next_frame_cache.state;
|
|
}
|
|
|
|
void ensure_bake_loaded(bake::SimulationNodeCache &node_cache,
|
|
bake::FrameCache &frame_cache) const
|
|
{
|
|
if (!frame_cache.state.items_by_id.is_empty()) {
|
|
return;
|
|
}
|
|
if (!node_cache.blobs_dir) {
|
|
return;
|
|
}
|
|
if (!frame_cache.meta_path) {
|
|
return;
|
|
}
|
|
bke::bake::DiskBlobReader blob_reader{*node_cache.blobs_dir};
|
|
fstream meta_file{*frame_cache.meta_path};
|
|
std::optional<bke::bake::BakeState> bake_state = bke::bake::deserialize_bake(
|
|
meta_file, blob_reader, *node_cache.blob_sharing);
|
|
if (!bake_state.has_value()) {
|
|
return;
|
|
}
|
|
frame_cache.state = std::move(*bake_state);
|
|
}
|
|
};
|
|
|
|
static void modifyGeometry(ModifierData *md,
|
|
const ModifierEvalContext *ctx,
|
|
bke::GeometrySet &geometry_set)
|
|
{
|
|
using namespace blender;
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
if (nmd->node_group == nullptr) {
|
|
return;
|
|
}
|
|
NodesModifierData *nmd_orig = reinterpret_cast<NodesModifierData *>(
|
|
BKE_modifier_get_original(ctx->object, &nmd->modifier));
|
|
|
|
const bNodeTree &tree = *nmd->node_group;
|
|
check_property_socket_sync(ctx->object, md);
|
|
|
|
tree.ensure_topology_cache();
|
|
const bNode *output_node = tree.group_output_node();
|
|
if (output_node == nullptr) {
|
|
BKE_modifier_set_error(ctx->object, md, "Node group must have a group output node");
|
|
geometry_set.clear();
|
|
return;
|
|
}
|
|
|
|
Span<const bNodeSocket *> group_outputs = output_node->input_sockets().drop_back(1);
|
|
if (group_outputs.is_empty()) {
|
|
BKE_modifier_set_error(ctx->object, md, "Node group must have an output socket");
|
|
geometry_set.clear();
|
|
return;
|
|
}
|
|
|
|
const bNodeSocket *first_output_socket = group_outputs[0];
|
|
if (!STREQ(first_output_socket->idname, "NodeSocketGeometry")) {
|
|
BKE_modifier_set_error(ctx->object, md, "Node group's first output must be a geometry");
|
|
geometry_set.clear();
|
|
return;
|
|
}
|
|
|
|
const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
|
|
nodes::ensure_geometry_nodes_lazy_function_graph(tree);
|
|
if (lf_graph_info == nullptr) {
|
|
BKE_modifier_set_error(ctx->object, md, "Cannot evaluate node group");
|
|
geometry_set.clear();
|
|
return;
|
|
}
|
|
|
|
bool use_orig_index_verts = false;
|
|
bool use_orig_index_edges = false;
|
|
bool use_orig_index_faces = false;
|
|
if (const Mesh *mesh = geometry_set.get_mesh()) {
|
|
use_orig_index_verts = CustomData_has_layer(&mesh->vert_data, CD_ORIGINDEX);
|
|
use_orig_index_edges = CustomData_has_layer(&mesh->edge_data, CD_ORIGINDEX);
|
|
use_orig_index_faces = CustomData_has_layer(&mesh->face_data, CD_ORIGINDEX);
|
|
}
|
|
|
|
nodes::GeoNodesCallData call_data;
|
|
|
|
nodes::GeoNodesModifierData modifier_eval_data{};
|
|
modifier_eval_data.depsgraph = ctx->depsgraph;
|
|
modifier_eval_data.self_object = ctx->object;
|
|
auto eval_log = std::make_unique<geo_log::GeoModifierLog>();
|
|
call_data.modifier_data = &modifier_eval_data;
|
|
|
|
NodesModifierSimulationParams simulation_params(*nmd, *ctx);
|
|
call_data.simulation_params = &simulation_params;
|
|
|
|
Set<ComputeContextHash> socket_log_contexts;
|
|
if (logging_enabled(ctx)) {
|
|
call_data.eval_log = eval_log.get();
|
|
|
|
find_socket_log_contexts(*nmd, *ctx, socket_log_contexts);
|
|
call_data.socket_log_contexts = &socket_log_contexts;
|
|
}
|
|
|
|
nodes::GeoNodesSideEffectNodes side_effect_nodes;
|
|
find_side_effect_nodes(*nmd, *ctx, side_effect_nodes);
|
|
call_data.side_effect_nodes = &side_effect_nodes;
|
|
|
|
bke::ModifierComputeContext modifier_compute_context{nullptr, nmd->modifier.name};
|
|
|
|
geometry_set = nodes::execute_geometry_nodes_on_geometry(tree,
|
|
nmd->settings.properties,
|
|
modifier_compute_context,
|
|
call_data,
|
|
std::move(geometry_set));
|
|
|
|
if (logging_enabled(ctx)) {
|
|
nmd_orig->runtime->eval_log = std::move(eval_log);
|
|
}
|
|
|
|
if (use_orig_index_verts || use_orig_index_edges || use_orig_index_faces) {
|
|
if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
|
|
/* Add #CD_ORIGINDEX layers if they don't exist already. This is required because the
|
|
* #eModifierTypeFlag_SupportsMapping flag is set. If the layers did not exist before, it is
|
|
* assumed that the output mesh does not have a mapping to the original mesh. */
|
|
if (use_orig_index_verts) {
|
|
CustomData_add_layer(&mesh->vert_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->totvert);
|
|
}
|
|
if (use_orig_index_edges) {
|
|
CustomData_add_layer(&mesh->edge_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->totedge);
|
|
}
|
|
if (use_orig_index_faces) {
|
|
CustomData_add_layer(&mesh->face_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->faces_num);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh)
|
|
{
|
|
bke::GeometrySet geometry_set = bke::GeometrySet::from_mesh(
|
|
mesh, bke::GeometryOwnershipType::Editable);
|
|
|
|
modifyGeometry(md, ctx, geometry_set);
|
|
|
|
Mesh *new_mesh = geometry_set.get_component_for_write<bke::MeshComponent>().release();
|
|
if (new_mesh == nullptr) {
|
|
return BKE_mesh_new_nomain(0, 0, 0, 0);
|
|
}
|
|
return new_mesh;
|
|
}
|
|
|
|
static void modify_geometry_set(ModifierData *md,
|
|
const ModifierEvalContext *ctx,
|
|
bke::GeometrySet *geometry_set)
|
|
{
|
|
modifyGeometry(md, ctx, *geometry_set);
|
|
}
|
|
|
|
struct AttributeSearchData {
|
|
uint32_t object_session_uid;
|
|
char modifier_name[MAX_NAME];
|
|
char socket_identifier[MAX_NAME];
|
|
bool is_output;
|
|
};
|
|
/* This class must not have a destructor, since it is used by buttons and freed with #MEM_freeN. */
|
|
BLI_STATIC_ASSERT(std::is_trivially_destructible_v<AttributeSearchData>, "");
|
|
|
|
static NodesModifierData *get_modifier_data(Main &bmain,
|
|
const wmWindowManager &wm,
|
|
const AttributeSearchData &data)
|
|
{
|
|
if (ED_screen_animation_playing(&wm)) {
|
|
/* Work around an issue where the attribute search exec function has stale pointers when data
|
|
* is reallocated when evaluating the node tree, causing a crash. This would be solved by
|
|
* allowing the UI search data to own arbitrary memory rather than just referencing it. */
|
|
return nullptr;
|
|
}
|
|
|
|
const Object *object = (Object *)BKE_libblock_find_session_uuid(
|
|
&bmain, ID_OB, data.object_session_uid);
|
|
if (object == nullptr) {
|
|
return nullptr;
|
|
}
|
|
ModifierData *md = BKE_modifiers_findby_name(object, data.modifier_name);
|
|
if (md == nullptr) {
|
|
return nullptr;
|
|
}
|
|
BLI_assert(md->type == eModifierType_Nodes);
|
|
return reinterpret_cast<NodesModifierData *>(md);
|
|
}
|
|
|
|
static geo_log::GeoTreeLog *get_root_tree_log(const NodesModifierData &nmd)
|
|
{
|
|
if (!nmd.runtime->eval_log) {
|
|
return nullptr;
|
|
}
|
|
bke::ModifierComputeContext compute_context{nullptr, nmd.modifier.name};
|
|
return &nmd.runtime->eval_log->get_tree_log(compute_context.hash());
|
|
}
|
|
|
|
static void attribute_search_update_fn(
|
|
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
|
|
{
|
|
AttributeSearchData &data = *static_cast<AttributeSearchData *>(arg);
|
|
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
|
|
if (nmd == nullptr) {
|
|
return;
|
|
}
|
|
if (nmd->node_group == nullptr) {
|
|
return;
|
|
}
|
|
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
|
|
if (tree_log == nullptr) {
|
|
return;
|
|
}
|
|
tree_log->ensure_existing_attributes();
|
|
nmd->node_group->ensure_topology_cache();
|
|
|
|
Vector<const bNodeSocket *> sockets_to_check;
|
|
if (data.is_output) {
|
|
for (const bNode *node : nmd->node_group->nodes_by_type("NodeGroupOutput")) {
|
|
for (const bNodeSocket *socket : node->input_sockets()) {
|
|
if (socket->type == SOCK_GEOMETRY) {
|
|
sockets_to_check.append(socket);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (const bNode *node : nmd->node_group->group_input_nodes()) {
|
|
for (const bNodeSocket *socket : node->output_sockets()) {
|
|
if (socket->type == SOCK_GEOMETRY) {
|
|
sockets_to_check.append(socket);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Set<StringRef> names;
|
|
Vector<const geo_log::GeometryAttributeInfo *> attributes;
|
|
for (const bNodeSocket *socket : sockets_to_check) {
|
|
const geo_log::ValueLog *value_log = tree_log->find_socket_value_log(*socket);
|
|
if (value_log == nullptr) {
|
|
continue;
|
|
}
|
|
if (const auto *geo_log = dynamic_cast<const geo_log::GeometryInfoLog *>(value_log)) {
|
|
for (const geo_log::GeometryAttributeInfo &attribute : geo_log->attributes) {
|
|
if (names.add(attribute.name)) {
|
|
attributes.append(&attribute);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ui::attribute_search_add_items(str, data.is_output, attributes.as_span(), items, is_first);
|
|
}
|
|
|
|
static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v)
|
|
{
|
|
if (item_v == nullptr) {
|
|
return;
|
|
}
|
|
AttributeSearchData &data = *static_cast<AttributeSearchData *>(data_v);
|
|
const auto &item = *static_cast<const geo_log::GeometryAttributeInfo *>(item_v);
|
|
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
|
|
if (nmd == nullptr) {
|
|
return;
|
|
}
|
|
|
|
const std::string attribute_prop_name = data.socket_identifier +
|
|
nodes::input_attribute_name_suffix();
|
|
IDProperty &name_property = *IDP_GetPropertyFromGroup(nmd->settings.properties,
|
|
attribute_prop_name.c_str());
|
|
IDP_AssignString(&name_property, item.name.c_str());
|
|
|
|
ED_undo_push(C, "Assign Attribute Name");
|
|
}
|
|
|
|
static void add_attribute_search_button(const bContext &C,
|
|
uiLayout *layout,
|
|
const NodesModifierData &nmd,
|
|
PointerRNA *md_ptr,
|
|
const StringRefNull rna_path_attribute_name,
|
|
const bNodeTreeInterfaceSocket &socket,
|
|
const bool is_output)
|
|
{
|
|
if (!nmd.runtime->eval_log) {
|
|
uiItemR(layout, md_ptr, rna_path_attribute_name.c_str(), UI_ITEM_NONE, "", ICON_NONE);
|
|
return;
|
|
}
|
|
|
|
uiBlock *block = uiLayoutGetBlock(layout);
|
|
uiBut *but = uiDefIconTextButR(block,
|
|
UI_BTYPE_SEARCH_MENU,
|
|
0,
|
|
ICON_NONE,
|
|
"",
|
|
0,
|
|
0,
|
|
10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */
|
|
UI_UNIT_Y,
|
|
md_ptr,
|
|
rna_path_attribute_name.c_str(),
|
|
0,
|
|
0.0f,
|
|
0.0f,
|
|
0.0f,
|
|
0.0f,
|
|
socket.description);
|
|
|
|
const Object *object = ED_object_context(&C);
|
|
BLI_assert(object != nullptr);
|
|
if (object == nullptr) {
|
|
return;
|
|
}
|
|
|
|
AttributeSearchData *data = MEM_new<AttributeSearchData>(__func__);
|
|
data->object_session_uid = object->id.session_uuid;
|
|
STRNCPY(data->modifier_name, nmd.modifier.name);
|
|
STRNCPY(data->socket_identifier, socket.identifier);
|
|
data->is_output = is_output;
|
|
|
|
UI_but_func_search_set_results_are_suggestions(but, true);
|
|
UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
|
|
UI_but_func_search_set(but,
|
|
nullptr,
|
|
attribute_search_update_fn,
|
|
static_cast<void *>(data),
|
|
true,
|
|
nullptr,
|
|
attribute_search_exec_fn,
|
|
nullptr);
|
|
|
|
char *attribute_name = RNA_string_get_alloc(
|
|
md_ptr, rna_path_attribute_name.c_str(), nullptr, 0, nullptr);
|
|
const bool access_allowed = bke::allow_procedural_attribute_access(attribute_name);
|
|
MEM_freeN(attribute_name);
|
|
if (!access_allowed) {
|
|
UI_but_flag_enable(but, UI_BUT_REDALERT);
|
|
}
|
|
}
|
|
|
|
static void add_attribute_search_or_value_buttons(const bContext &C,
|
|
uiLayout *layout,
|
|
const NodesModifierData &nmd,
|
|
PointerRNA *md_ptr,
|
|
const bNodeTreeInterfaceSocket &socket)
|
|
{
|
|
const StringRefNull identifier = socket.identifier;
|
|
const bNodeSocketType *typeinfo = socket.socket_typeinfo();
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
|
char socket_id_esc[MAX_NAME * 2];
|
|
BLI_str_escape(socket_id_esc, identifier.c_str(), sizeof(socket_id_esc));
|
|
const std::string rna_path = "[\"" + std::string(socket_id_esc) + "\"]";
|
|
const std::string rna_path_attribute_name = "[\"" + std::string(socket_id_esc) +
|
|
nodes::input_attribute_name_suffix() + "\"]";
|
|
|
|
/* We're handling this manually in this case. */
|
|
uiLayoutSetPropDecorate(layout, false);
|
|
|
|
uiLayout *split = uiLayoutSplit(layout, 0.4f, false);
|
|
uiLayout *name_row = uiLayoutRow(split, false);
|
|
uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT);
|
|
|
|
const std::optional<StringRef> attribute_name = nodes::input_attribute_name_get(
|
|
*nmd.settings.properties, socket);
|
|
if (type == SOCK_BOOLEAN && !attribute_name) {
|
|
uiItemL(name_row, "", ICON_NONE);
|
|
}
|
|
else {
|
|
uiItemL(name_row, socket.name ? IFACE_(socket.name) : "", ICON_NONE);
|
|
}
|
|
|
|
uiLayout *prop_row = uiLayoutRow(split, true);
|
|
if (type == SOCK_BOOLEAN) {
|
|
uiLayoutSetPropSep(prop_row, false);
|
|
uiLayoutSetAlignment(prop_row, UI_LAYOUT_ALIGN_EXPAND);
|
|
}
|
|
|
|
if (attribute_name) {
|
|
add_attribute_search_button(C, prop_row, nmd, md_ptr, rna_path_attribute_name, socket, false);
|
|
uiItemL(layout, "", ICON_BLANK1);
|
|
}
|
|
else {
|
|
const char *name = type == SOCK_BOOLEAN ? (socket.name ? IFACE_(socket.name) : "") : "";
|
|
uiItemR(prop_row, md_ptr, rna_path.c_str(), UI_ITEM_NONE, name, ICON_NONE);
|
|
uiItemDecoratorR(layout, md_ptr, rna_path.c_str(), -1);
|
|
}
|
|
|
|
PointerRNA props;
|
|
uiItemFullO(prop_row,
|
|
"object.geometry_nodes_input_attribute_toggle",
|
|
"",
|
|
ICON_SPREADSHEET,
|
|
nullptr,
|
|
WM_OP_INVOKE_DEFAULT,
|
|
UI_ITEM_NONE,
|
|
&props);
|
|
RNA_string_set(&props, "modifier_name", nmd.modifier.name);
|
|
RNA_string_set(&props, "input_name", socket.identifier);
|
|
}
|
|
|
|
/* 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(const bContext &C,
|
|
uiLayout *layout,
|
|
NodesModifierData *nmd,
|
|
PointerRNA *bmain_ptr,
|
|
PointerRNA *md_ptr,
|
|
const bNodeTreeInterfaceSocket &socket,
|
|
const int socket_index)
|
|
{
|
|
const StringRefNull identifier = socket.identifier;
|
|
/* The property should be created in #MOD_nodes_update_interface with the correct type. */
|
|
IDProperty *property = IDP_GetPropertyFromGroup(nmd->settings.properties, identifier.c_str());
|
|
|
|
/* 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 || !nodes::id_property_type_matches_socket(socket, *property)) {
|
|
return;
|
|
}
|
|
|
|
char socket_id_esc[MAX_NAME * 2];
|
|
BLI_str_escape(socket_id_esc, identifier.c_str(), sizeof(socket_id_esc));
|
|
|
|
char rna_path[sizeof(socket_id_esc) + 4];
|
|
SNPRINTF(rna_path, "[\"%s\"]", socket_id_esc);
|
|
|
|
uiLayout *row = uiLayoutRow(layout, true);
|
|
uiLayoutSetPropDecorate(row, true);
|
|
|
|
/* 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. */
|
|
const bNodeSocketType *typeinfo = socket.socket_typeinfo();
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
|
const char *name = socket.name ? IFACE_(socket.name) : "";
|
|
switch (type) {
|
|
case SOCK_OBJECT: {
|
|
uiItemPointerR(row, md_ptr, rna_path, bmain_ptr, "objects", name, ICON_OBJECT_DATA);
|
|
break;
|
|
}
|
|
case SOCK_COLLECTION: {
|
|
uiItemPointerR(
|
|
row, md_ptr, rna_path, bmain_ptr, "collections", name, ICON_OUTLINER_COLLECTION);
|
|
break;
|
|
}
|
|
case SOCK_MATERIAL: {
|
|
uiItemPointerR(row, md_ptr, rna_path, bmain_ptr, "materials", name, ICON_MATERIAL);
|
|
break;
|
|
}
|
|
case SOCK_TEXTURE: {
|
|
uiItemPointerR(row, md_ptr, rna_path, bmain_ptr, "textures", name, ICON_TEXTURE);
|
|
break;
|
|
}
|
|
case SOCK_IMAGE: {
|
|
uiItemPointerR(row, md_ptr, rna_path, bmain_ptr, "images", name, ICON_IMAGE);
|
|
break;
|
|
}
|
|
case SOCK_BOOLEAN: {
|
|
if (is_layer_selection_field(socket)) {
|
|
uiItemR(row, md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
|
|
break;
|
|
}
|
|
ATTR_FALLTHROUGH;
|
|
}
|
|
default: {
|
|
if (nodes::input_has_attribute_toggle(*nmd->node_group, socket_index)) {
|
|
add_attribute_search_or_value_buttons(C, row, *nmd, md_ptr, socket);
|
|
}
|
|
else {
|
|
uiItemR(row, md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
|
|
}
|
|
}
|
|
}
|
|
if (!nodes::input_has_attribute_toggle(*nmd->node_group, socket_index)) {
|
|
uiItemL(row, "", ICON_BLANK1);
|
|
}
|
|
}
|
|
|
|
static void draw_property_for_output_socket(const bContext &C,
|
|
uiLayout *layout,
|
|
const NodesModifierData &nmd,
|
|
PointerRNA *md_ptr,
|
|
const bNodeTreeInterfaceSocket &socket)
|
|
{
|
|
const StringRefNull identifier = socket.identifier;
|
|
char socket_id_esc[MAX_NAME * 2];
|
|
BLI_str_escape(socket_id_esc, identifier.c_str(), sizeof(socket_id_esc));
|
|
const std::string rna_path_attribute_name = "[\"" + StringRef(socket_id_esc) +
|
|
nodes::input_attribute_name_suffix() + "\"]";
|
|
|
|
uiLayout *split = uiLayoutSplit(layout, 0.4f, false);
|
|
uiLayout *name_row = uiLayoutRow(split, false);
|
|
uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT);
|
|
uiItemL(name_row, socket.name ? socket.name : "", ICON_NONE);
|
|
|
|
uiLayout *row = uiLayoutRow(split, true);
|
|
add_attribute_search_button(C, row, nmd, md_ptr, rna_path_attribute_name, socket, true);
|
|
}
|
|
|
|
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);
|
|
/* Decorators are added manually for supported properties because the
|
|
* attribute/value toggle requires a manually built layout anyway. */
|
|
uiLayoutSetPropDecorate(layout, false);
|
|
|
|
if (!(nmd->flag & NODES_MODIFIER_HIDE_DATABLOCK_SELECTOR)) {
|
|
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);
|
|
|
|
nmd->node_group->ensure_interface_cache();
|
|
|
|
for (const int socket_index : nmd->node_group->interface_inputs().index_range()) {
|
|
const bNodeTreeInterfaceSocket *socket = nmd->node_group->interface_inputs()[socket_index];
|
|
if (is_layer_selection_field(*socket) ||
|
|
!(socket->flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
|
|
draw_property_for_socket(*C, layout, nmd, &bmain_ptr, ptr, *socket, socket_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw node warnings. */
|
|
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
|
|
if (tree_log != nullptr) {
|
|
tree_log->ensure_node_warnings();
|
|
for (const geo_log::NodeWarning &warning : tree_log->all_warnings) {
|
|
if (warning.type != geo_log::NodeWarningType::Info) {
|
|
uiItemL(layout, warning.message.c_str(), ICON_ERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
modifier_panel_end(layout, ptr);
|
|
}
|
|
|
|
static void output_attribute_panel_draw(const bContext *C, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
|
|
NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data);
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
uiLayoutSetPropDecorate(layout, true);
|
|
|
|
bool has_output_attribute = false;
|
|
if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) {
|
|
for (const bNodeTreeInterfaceSocket *socket : nmd->node_group->interface_outputs()) {
|
|
const bNodeSocketType *typeinfo = socket->socket_typeinfo();
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) :
|
|
SOCK_CUSTOM;
|
|
if (nodes::socket_type_has_attribute_toggle(type)) {
|
|
has_output_attribute = true;
|
|
draw_property_for_output_socket(*C, layout, *nmd, ptr, *socket);
|
|
}
|
|
}
|
|
}
|
|
if (!has_output_attribute) {
|
|
uiItemL(layout, TIP_("No group output attributes connected"), ICON_INFO);
|
|
}
|
|
}
|
|
|
|
static void internal_dependencies_panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
|
|
NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data);
|
|
|
|
uiLayout *col = uiLayoutColumn(layout, false);
|
|
uiLayoutSetPropSep(col, true);
|
|
uiLayoutSetPropDecorate(col, false);
|
|
uiItemR(col, ptr, "simulation_bake_directory", UI_ITEM_NONE, IFACE_("Bake"), ICON_NONE);
|
|
|
|
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
|
|
if (tree_log == nullptr) {
|
|
return;
|
|
}
|
|
|
|
tree_log->ensure_used_named_attributes();
|
|
const Map<StringRefNull, geo_log::NamedAttributeUsage> &usage_by_attribute =
|
|
tree_log->used_named_attributes;
|
|
|
|
if (usage_by_attribute.is_empty()) {
|
|
uiItemL(layout, IFACE_("No named attributes used"), ICON_INFO);
|
|
return;
|
|
}
|
|
|
|
struct NameWithUsage {
|
|
StringRefNull name;
|
|
geo_log::NamedAttributeUsage usage;
|
|
};
|
|
|
|
Vector<NameWithUsage> sorted_used_attribute;
|
|
for (auto &&item : usage_by_attribute.items()) {
|
|
sorted_used_attribute.append({item.key, item.value});
|
|
}
|
|
std::sort(sorted_used_attribute.begin(),
|
|
sorted_used_attribute.end(),
|
|
[](const NameWithUsage &a, const NameWithUsage &b) {
|
|
return BLI_strcasecmp_natural(a.name.c_str(), b.name.c_str()) <= 0;
|
|
});
|
|
|
|
for (const NameWithUsage &attribute : sorted_used_attribute) {
|
|
const StringRefNull attribute_name = attribute.name;
|
|
const geo_log::NamedAttributeUsage usage = attribute.usage;
|
|
|
|
/* #uiLayoutRowWithHeading doesn't seem to work in this case. */
|
|
uiLayout *split = uiLayoutSplit(layout, 0.4f, false);
|
|
|
|
std::stringstream ss;
|
|
Vector<std::string> usages;
|
|
if ((usage & geo_log::NamedAttributeUsage::Read) != geo_log::NamedAttributeUsage::None) {
|
|
usages.append(TIP_("Read"));
|
|
}
|
|
if ((usage & geo_log::NamedAttributeUsage::Write) != geo_log::NamedAttributeUsage::None) {
|
|
usages.append(TIP_("Write"));
|
|
}
|
|
if ((usage & geo_log::NamedAttributeUsage::Remove) != geo_log::NamedAttributeUsage::None) {
|
|
usages.append(TIP_("Remove"));
|
|
}
|
|
for (const int i : usages.index_range()) {
|
|
ss << usages[i];
|
|
if (i < usages.size() - 1) {
|
|
ss << ", ";
|
|
}
|
|
}
|
|
|
|
uiLayout *row = uiLayoutRow(split, false);
|
|
uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_RIGHT);
|
|
uiLayoutSetActive(row, false);
|
|
uiItemL(row, ss.str().c_str(), ICON_NONE);
|
|
|
|
row = uiLayoutRow(split, false);
|
|
uiItemL(row, attribute_name.c_str(), ICON_NONE);
|
|
}
|
|
}
|
|
|
|
static void panel_register(ARegionType *region_type)
|
|
{
|
|
using namespace blender;
|
|
PanelType *panel_type = modifier_panel_register(region_type, eModifierType_Nodes, panel_draw);
|
|
modifier_subpanel_register(region_type,
|
|
"output_attributes",
|
|
N_("Output Attributes"),
|
|
nullptr,
|
|
output_attribute_panel_draw,
|
|
panel_type);
|
|
modifier_subpanel_register(region_type,
|
|
"internal_dependencies",
|
|
N_("Internal Dependencies"),
|
|
nullptr,
|
|
internal_dependencies_panel_draw,
|
|
panel_type);
|
|
}
|
|
|
|
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
|
|
{
|
|
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
|
|
|
|
BLO_write_struct(writer, NodesModifierData, nmd);
|
|
|
|
BLO_write_string(writer, nmd->simulation_bake_directory);
|
|
|
|
if (nmd->settings.properties != nullptr) {
|
|
Map<IDProperty *, IDPropertyUIDataBool *> boolean_props;
|
|
if (!BLO_write_is_undo(writer)) {
|
|
/* Boolean properties are added automatically for boolean node group inputs. Integer
|
|
* properties are automatically converted to boolean sockets where applicable as well.
|
|
* However, boolean properties will crash old versions of Blender, so convert them to integer
|
|
* properties for writing. The actual value is stored in the same variable for both types */
|
|
LISTBASE_FOREACH (IDProperty *, prop, &nmd->settings.properties->data.group) {
|
|
if (prop->type == IDP_BOOLEAN) {
|
|
boolean_props.add_new(prop, reinterpret_cast<IDPropertyUIDataBool *>(prop->ui_data));
|
|
prop->type = IDP_INT;
|
|
prop->ui_data = 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);
|
|
|
|
BLO_write_struct_array(writer, NodesModifierBake, nmd->bakes_num, nmd->bakes);
|
|
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
|
|
BLO_write_string(writer, bake.directory);
|
|
}
|
|
|
|
if (!BLO_write_is_undo(writer)) {
|
|
LISTBASE_FOREACH (IDProperty *, prop, &nmd->settings.properties->data.group) {
|
|
if (prop->type == IDP_INT) {
|
|
if (IDPropertyUIDataBool **ui_data = boolean_props.lookup_ptr(prop)) {
|
|
prop->type = IDP_BOOLEAN;
|
|
if (ui_data) {
|
|
prop->ui_data = reinterpret_cast<IDPropertyUIData *>(*ui_data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void blend_read(BlendDataReader *reader, ModifierData *md)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
BLO_read_data_address(reader, &nmd->simulation_bake_directory);
|
|
if (nmd->node_group == nullptr) {
|
|
nmd->settings.properties = nullptr;
|
|
}
|
|
else {
|
|
BLO_read_data_address(reader, &nmd->settings.properties);
|
|
IDP_BlendDataRead(reader, &nmd->settings.properties);
|
|
}
|
|
|
|
BLO_read_data_address(reader, &nmd->bakes);
|
|
for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) {
|
|
BLO_read_data_address(reader, &bake.directory);
|
|
}
|
|
|
|
nmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
|
|
nmd->runtime->cache = std::make_shared<bake::ModifierCache>();
|
|
}
|
|
|
|
static void copy_data(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->bakes) {
|
|
tnmd->bakes = static_cast<NodesModifierBake *>(MEM_dupallocN(nmd->bakes));
|
|
for (const int i : IndexRange(nmd->bakes_num)) {
|
|
NodesModifierBake &bake = tnmd->bakes[i];
|
|
if (bake.directory) {
|
|
bake.directory = BLI_strdup(bake.directory);
|
|
}
|
|
}
|
|
}
|
|
|
|
tnmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
|
|
|
|
if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) {
|
|
/* Share the simulation cache between the original and evaluated modifier. */
|
|
tnmd->runtime->cache = nmd->runtime->cache;
|
|
/* Keep bake path in the evaluated modifier. */
|
|
tnmd->simulation_bake_directory = nmd->simulation_bake_directory ?
|
|
BLI_strdup(nmd->simulation_bake_directory) :
|
|
nullptr;
|
|
}
|
|
else {
|
|
tnmd->runtime->cache = std::make_shared<bake::ModifierCache>();
|
|
update_existing_bake_caches(*tnmd);
|
|
/* Clear the bake path when duplicating. */
|
|
tnmd->simulation_bake_directory = nullptr;
|
|
}
|
|
|
|
if (nmd->settings.properties != nullptr) {
|
|
tnmd->settings.properties = IDP_CopyProperty_ex(nmd->settings.properties, flag);
|
|
}
|
|
}
|
|
|
|
static void free_data(ModifierData *md)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
if (nmd->settings.properties != nullptr) {
|
|
IDP_FreeProperty_ex(nmd->settings.properties, false);
|
|
nmd->settings.properties = nullptr;
|
|
}
|
|
|
|
for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) {
|
|
MEM_SAFE_FREE(bake.directory);
|
|
}
|
|
MEM_SAFE_FREE(nmd->bakes);
|
|
|
|
MEM_SAFE_FREE(nmd->simulation_bake_directory);
|
|
MEM_delete(nmd->runtime);
|
|
}
|
|
|
|
static void required_data_mask(ModifierData * /*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;
|
|
}
|
|
|
|
} // namespace blender
|
|
|
|
ModifierTypeInfo modifierType_Nodes = {
|
|
/*idname*/ "GeometryNodes",
|
|
/*name*/ N_("GeometryNodes"),
|
|
/*struct_name*/ "NodesModifierData",
|
|
/*struct_size*/ sizeof(NodesModifierData),
|
|
/*srna*/ &RNA_NodesModifier,
|
|
/*type*/ ModifierTypeType::Constructive,
|
|
/*flags*/
|
|
static_cast<ModifierTypeFlag>(
|
|
eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs |
|
|
eModifierTypeFlag_SupportsEditmode | eModifierTypeFlag_EnableInEditmode |
|
|
eModifierTypeFlag_SupportsMapping | eModifierTypeFlag_AcceptsGreasePencil),
|
|
/*icon*/ ICON_GEOMETRY_NODES,
|
|
|
|
/*copy_data*/ blender::copy_data,
|
|
|
|
/*deform_verts*/ nullptr,
|
|
/*deform_matrices*/ nullptr,
|
|
/*deform_verts_EM*/ nullptr,
|
|
/*deform_matrices_EM*/ nullptr,
|
|
/*modify_mesh*/ blender::modify_mesh,
|
|
/*modify_geometry_set*/ blender::modify_geometry_set,
|
|
|
|
/*init_data*/ blender::init_data,
|
|
/*required_data_mask*/ blender::required_data_mask,
|
|
/*free_data*/ blender::free_data,
|
|
/*is_disabled*/ blender::is_disabled,
|
|
/*update_depsgraph*/ blender::update_depsgraph,
|
|
/*depends_on_time*/ blender::depends_on_time,
|
|
/*depends_on_normals*/ nullptr,
|
|
/*foreach_ID_link*/ blender::foreach_ID_link,
|
|
/*foreach_tex_link*/ blender::foreach_tex_link,
|
|
/*free_runtime_data*/ nullptr,
|
|
/*panel_register*/ blender::panel_register,
|
|
/*blend_write*/ blender::blend_write,
|
|
/*blend_read*/ blender::blend_read,
|
|
};
|