Files
test2/source/blender/editors/geometry/node_group_operator.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1475 lines
50 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edcurves
*/
#include "BLI_listbase.h"
#include "BLI_path_utils.hh"
#include "BLI_rect.h"
#include "BLI_string.h"
#include "ED_curves.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ED_select_utils.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "BKE_asset.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_context.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "BKE_editmesh.hh"
#include "BKE_geometry_set.hh"
2024-01-23 15:18:09 -05:00
#include "BKE_layer.hh"
2024-01-15 12:44:04 -05:00
#include "BKE_lib_id.hh"
#include "BKE_lib_query.hh"
#include "BKE_main.hh"
#include "BKE_material.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_wrapper.hh"
#include "BKE_node_runtime.hh"
#include "BKE_object.hh"
2025-01-07 13:46:03 -05:00
#include "BKE_paint.hh"
#include "BKE_paint_bvh.hh"
#include "BKE_pointcloud.hh"
#include "BKE_report.hh"
#include "BKE_scene.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.hh"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_query.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "ED_asset.hh"
#include "ED_asset_menu_utils.hh"
#include "ED_geometry.hh"
#include "ED_mesh.hh"
#include "ED_sculpt.hh"
#include "BLT_translation.hh"
Geometry Nodes: improve detecting data-block dependencies Previously, the data-block dependencies were always detected in `update_depsgraph` in `MOD_nodes.cc`. This would only be called when something called `DEG_relations_tag_update` before. We don't want to trigger a depsgraph rebuild after each operation in the node editor, as that would be expensive. However, that also meant that we often had to add data-block dependencies that are not actually used, but might be used if the user changed e.g. a link. A typical example for that is a object socket that has a default value, but the socket is also linked. Now, the dependencies referenced by the node tree are collected by the node tree update code which runs after all changes. This way we can detect whether the dependencies have changed. Only if they have changed, a depsgraph rebuild is triggered. This now allows also taking into account the mute status of nodes and whether an input is linked. There are still more things that could be taken into account. Most obviously whether a node is connected to an output. This can be done later. The most tricky aspect here is probably that we also have to consider all viewer nodes as output, because at the time the node runs, it's not known which viewer will actually be used (which depends on other editors). This also cleans up some special cases we had for e.g. the scene time node where we always had to trigger a depsgraph rebuild when it was added/removed because of its time dependence. This is now part of a more general system. This fixes #109219. Pull Request: https://projects.blender.org/blender/blender/pulls/131446
2024-12-05 18:02:14 +01:00
#include "NOD_geometry_nodes_dependencies.hh"
#include "NOD_geometry_nodes_execute.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
Nodes: automatically gray out input values that don't affect the output This patch automatically grays out input values which can't affect the output currently. It works with inputs of group nodes, geometry nodes modifiers and node tools. To achieve this, it analyses the node tree and partially evaluates it to figure out which group inputs are currently not linked to an output or are disabled by e.g. some switch node. Original proposal: https://devtalk.blender.org/t/dynamic-socket-visibility/31874 Related info in blog post: https://code.blender.org/2023/11/geometry-nodes-workshop-november-2023/#dynamic-socket-visibility Follow up task for designing a UI that allows hiding sockets: #132706 Limitations: * The inferencing does not update correctly when a socket starts being animated/driven. I haven't found a good way to invalidate the cache in a good way reliably yet. It's only a very short term problem though. It fixes itself after the next modification of the node tree and is only noticeable when animating some specific sockets such as the switch node condition. * Whether a socket is grayed out is not exposed in the Python API yet. That will be done separately. * Only a partial evaluation is done to determine if an input affects an output. There should be no cases where a socket is found to be unused when it can actually affect the output. However, there can be cases where a socket is inferenced to be used even if it is not due to some complex condition. Depending on the exact circumstances, this can either be improved or the condition in the node tree should be simplified. Pull Request: https://projects.blender.org/blender/blender/pulls/132219
2025-01-21 12:53:24 +01:00
#include "NOD_socket_usage_inference.hh"
#include "AS_asset_catalog.hh"
#include "AS_asset_catalog_path.hh"
#include "AS_asset_catalog_tree.hh"
#include "AS_asset_library.hh"
#include "AS_asset_representation.hh"
#include "geometry_intern.hh"
#include <fmt/format.h>
namespace geo_log = blender::nodes::geo_eval_log;
namespace blender::ed::geometry {
/* -------------------------------------------------------------------- */
/** \name Operator
* \{ */
static const bNodeTree *get_asset_or_local_node_group(const bContext &C,
PointerRNA &ptr,
ReportList *reports)
{
Main &bmain = *CTX_data_main(&C);
if (bNodeTree *group = reinterpret_cast<bNodeTree *>(
WM_operator_properties_id_lookup_from_name_or_session_uid(&bmain, &ptr, ID_NT)))
{
return group;
}
const asset_system::AssetRepresentation *asset =
asset::operator_asset_reference_props_get_asset_from_all_library(C, ptr, reports);
if (!asset) {
return nullptr;
}
return reinterpret_cast<bNodeTree *>(asset::asset_local_id_ensure_imported(bmain, *asset));
}
static const bNodeTree *get_node_group(const bContext &C, PointerRNA &ptr, ReportList *reports)
{
const bNodeTree *group = get_asset_or_local_node_group(C, ptr, reports);
if (!group) {
return nullptr;
}
if (group->type != NTREE_GEOMETRY) {
if (reports) {
BKE_report(reports, RPT_ERROR, "Asset is not a geometry node group");
}
return nullptr;
}
return group;
}
GeoOperatorLog::~GeoOperatorLog() = default;
/**
* The socket value log is stored statically so it can be used in the node editor. A fancier
* storage system shouldn't be necessary, since the goal is just to be able to debug intermediate
* values when building a tool.
*/
static GeoOperatorLog &get_static_eval_log()
{
static GeoOperatorLog log;
return log;
}
const GeoOperatorLog &node_group_operator_static_eval_log()
{
return get_static_eval_log();
}
/** Find all the visible node editors to log values for. */
static void find_socket_log_contexts(const Main &bmain,
Set<ComputeContextHash> &r_socket_log_contexts)
{
wmWindowManager *wm = static_cast<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;
}
ComputeContextBuilder compute_context_builder;
compute_context_builder.push<bke::OperatorComputeContext>();
const Map<const bke::bNodeTreeZone *, ComputeContextHash> hash_by_zone =
geo_log::GeoModifierLog::get_context_hash_by_zone_for_node_editor(
snode, compute_context_builder);
for (const ComputeContextHash &hash : hash_by_zone.values()) {
r_socket_log_contexts.add(hash);
}
}
}
}
}
/**
* This class adds a user to shared mesh data, requiring modifications of the mesh to reallocate
* the data and its sharing info. This allows tracking which data is modified without having to
* explicitly compare it.
*/
class MeshState {
VectorSet<const ImplicitSharingInfo *> sharing_infos_;
public:
MeshState(const Mesh &mesh)
{
if (mesh.runtime->face_offsets_sharing_info) {
this->freeze_shared_state(*mesh.runtime->face_offsets_sharing_info);
}
mesh.attributes().foreach_attribute([&](const bke::AttributeIter &iter) {
const bke::GAttributeReader attribute = iter.get();
if (attribute.varray.size() == 0) {
return;
}
this->freeze_shared_state(*attribute.sharing_info);
});
}
void freeze_shared_state(const ImplicitSharingInfo &sharing_info)
{
if (sharing_infos_.add(&sharing_info)) {
sharing_info.add_user();
}
}
~MeshState()
{
for (const ImplicitSharingInfo *sharing_info : sharing_infos_) {
sharing_info->remove_user_and_delete_if_last();
}
}
};
/**
* Geometry nodes currently requires working on "evaluated" data-blocks (rather than "original"
* data-blocks that are part of a #Main data-base). This could change in the future, but for now,
* we need to create evaluated copies of geometry before passing it to geometry nodes. Implicit
* sharing lets us avoid copying attribute data though.
*/
static bke::GeometrySet get_original_geometry_eval_copy(Depsgraph &depsgraph,
Object &object,
nodes::GeoNodesOperatorData &operator_data,
Vector<MeshState> &orig_mesh_states)
{
switch (object.type) {
case OB_CURVES: {
Curves *curves = BKE_curves_copy_for_eval(static_cast<const Curves *>(object.data));
return bke::GeometrySet::from_curves(curves);
}
case OB_POINTCLOUD: {
PointCloud *points = BKE_pointcloud_copy_for_eval(
static_cast<const PointCloud *>(object.data));
return bke::GeometrySet::from_pointcloud(points);
}
case OB_MESH: {
const Mesh *mesh = static_cast<const Mesh *>(object.data);
if (std::shared_ptr<BMEditMesh> &em = mesh->runtime->edit_mesh) {
operator_data.active_point_index = BM_mesh_active_vert_index_get(em->bm);
operator_data.active_edge_index = BM_mesh_active_edge_index_get(em->bm);
operator_data.active_face_index = BM_mesh_active_face_index_get(em->bm, false, true);
Mesh *mesh_copy = BKE_mesh_wrapper_from_editmesh(em, nullptr, mesh);
BKE_mesh_wrapper_ensure_mdata(mesh_copy);
Mesh *final_copy = BKE_mesh_copy_for_eval(*mesh_copy);
BKE_id_free(nullptr, mesh_copy);
return bke::GeometrySet::from_mesh(final_copy);
}
if (bke::pbvh::Tree *pbvh = bke::object::pbvh_get(object)) {
/* Currently many sculpt mode operations do not tag normals dirty (see use of
* #Mesh::tag_positions_changed_no_normals()), so access within geometry nodes cannot
* know that normals are out of date and recalculate them. Update them here instead. */
bke::pbvh::update_normals(depsgraph, object, *pbvh);
}
Mesh *mesh_copy = BKE_mesh_copy_for_eval(*mesh);
orig_mesh_states.append_as(*mesh_copy);
return bke::GeometrySet::from_mesh(mesh_copy);
}
default:
return {};
}
}
static void store_result_geometry(const wmOperator &op,
const Depsgraph &depsgraph,
Main &bmain,
Scene &scene,
Object &object,
const RegionView3D *rv3d,
bke::GeometrySet geometry)
{
geometry.ensure_owns_direct_data();
switch (object.type) {
case OB_CURVES: {
Curves &curves = *static_cast<Curves *>(object.data);
Curves *new_curves = geometry.get_curves_for_write();
if (!new_curves) {
curves.geometry.wrap() = {};
break;
}
/* Anonymous attributes shouldn't be available on the applied geometry. */
new_curves->geometry.wrap().attributes_for_write().remove_anonymous();
curves.geometry.wrap() = std::move(new_curves->geometry.wrap());
BKE_object_material_from_eval_data(&bmain, &object, &new_curves->id);
DEG_id_tag_update(&curves.id, ID_RECALC_GEOMETRY);
break;
}
case OB_POINTCLOUD: {
PointCloud &points = *static_cast<PointCloud *>(object.data);
PointCloud *new_points =
geometry.get_component_for_write<bke::PointCloudComponent>().release();
if (!new_points) {
CustomData_free(&points.pdata);
points.totpoint = 0;
break;
}
/* Anonymous attributes shouldn't be available on the applied geometry. */
new_points->attributes_for_write().remove_anonymous();
BKE_object_material_from_eval_data(&bmain, &object, &new_points->id);
BKE_pointcloud_nomain_to_pointcloud(new_points, &points);
DEG_id_tag_update(&points.id, ID_RECALC_GEOMETRY);
break;
}
case OB_MESH: {
Mesh &mesh = *static_cast<Mesh *>(object.data);
const bool has_shape_keys = mesh.key != nullptr;
Mesh *new_mesh = geometry.get_component_for_write<bke::MeshComponent>().release();
if (new_mesh) {
/* Anonymous attributes shouldn't be available on the applied geometry. */
new_mesh->attributes_for_write().remove_anonymous();
BKE_object_material_from_eval_data(&bmain, &object, &new_mesh->id);
}
else {
new_mesh = BKE_mesh_new_nomain(0, 0, 0, 0);
}
if (object.mode == OB_MODE_SCULPT) {
sculpt_paint::store_mesh_from_eval(op, scene, depsgraph, rv3d, object, new_mesh);
}
else if (object.mode == OB_MODE_EDIT) {
EDBM_mesh_make_from_mesh(&object, new_mesh, scene.toolsettings->selectmode, true);
BKE_editmesh_looptris_and_normals_calc(mesh.runtime->edit_mesh.get());
BKE_id_free(nullptr, new_mesh);
DEG_id_tag_update(&mesh.id, ID_RECALC_GEOMETRY);
}
else {
BKE_mesh_nomain_to_mesh(new_mesh, &mesh, &object);
DEG_id_tag_update(&mesh.id, ID_RECALC_GEOMETRY);
}
if (has_shape_keys && !mesh.key) {
BKE_report(op.reports, RPT_WARNING, "Mesh shape key data removed");
}
break;
}
}
}
/**
* Gather IDs used by the node group, and the node group itself if there are any. We need to use
* *all* IDs because the only mechanism we have to replace the socket ID pointers with their
* evaluated counterparts is evaluating the node group data-block itself.
*/
static void gather_node_group_ids(const bNodeTree &node_tree, Set<ID *> &ids)
{
const int orig_size = ids.size();
Geometry Nodes: improve detecting data-block dependencies Previously, the data-block dependencies were always detected in `update_depsgraph` in `MOD_nodes.cc`. This would only be called when something called `DEG_relations_tag_update` before. We don't want to trigger a depsgraph rebuild after each operation in the node editor, as that would be expensive. However, that also meant that we often had to add data-block dependencies that are not actually used, but might be used if the user changed e.g. a link. A typical example for that is a object socket that has a default value, but the socket is also linked. Now, the dependencies referenced by the node tree are collected by the node tree update code which runs after all changes. This way we can detect whether the dependencies have changed. Only if they have changed, a depsgraph rebuild is triggered. This now allows also taking into account the mute status of nodes and whether an input is linked. There are still more things that could be taken into account. Most obviously whether a node is connected to an output. This can be done later. The most tricky aspect here is probably that we also have to consider all viewer nodes as output, because at the time the node runs, it's not known which viewer will actually be used (which depends on other editors). This also cleans up some special cases we had for e.g. the scene time node where we always had to trigger a depsgraph rebuild when it was added/removed because of its time dependence. This is now part of a more general system. This fixes #109219. Pull Request: https://projects.blender.org/blender/blender/pulls/131446
2024-12-05 18:02:14 +01:00
BLI_assert(node_tree.runtime->geometry_nodes_eval_dependencies);
for (ID *id : node_tree.runtime->geometry_nodes_eval_dependencies->ids.values()) {
ids.add(id);
}
if (ids.size() != orig_size) {
/* Only evaluate the node group if it references data-blocks. In that case it needs to be
* evaluated so that ID pointers are switched to point to evaluated data-blocks. */
ids.add(const_cast<ID *>(&node_tree.id));
}
}
static const bNodeTreeInterfaceSocket *find_group_input_by_identifier(const bNodeTree &node_group,
const StringRef identifier)
{
for (const bNodeTreeInterfaceSocket *input : node_group.interface_inputs()) {
if (input->identifier == identifier) {
return input;
}
}
return nullptr;
}
static std::optional<ID_Type> socket_type_to_id_type(const eNodeSocketDatatype socket_type)
{
switch (socket_type) {
case SOCK_CUSTOM:
case SOCK_FLOAT:
case SOCK_VECTOR:
case SOCK_RGBA:
case SOCK_SHADER:
case SOCK_BOOLEAN:
case SOCK_INT:
case SOCK_STRING:
case SOCK_GEOMETRY:
case SOCK_ROTATION:
case SOCK_MENU:
case SOCK_MATRIX:
return std::nullopt;
case SOCK_OBJECT:
return ID_OB;
case SOCK_IMAGE:
return ID_IM;
case SOCK_COLLECTION:
return ID_GR;
case SOCK_TEXTURE:
return ID_TE;
case SOCK_MATERIAL:
return ID_MA;
}
return std::nullopt;
}
/**
* Gather IDs referenced from node group input properties (the redo panel). In the end, the group
* input properties will be copied to contain evaluated data-blocks from the active and/or an extra
* depsgraph.
*/
static Map<StringRef, ID *> gather_input_ids(const Main &bmain,
const bNodeTree &node_group,
const IDProperty &properties)
{
Map<StringRef, ID *> ids;
IDP_foreach_property(
&const_cast<IDProperty &>(properties), IDP_TYPE_FILTER_STRING, [&](IDProperty *prop) {
const bNodeTreeInterfaceSocket *input = find_group_input_by_identifier(node_group,
prop->name);
if (!input) {
return;
}
const std::optional<ID_Type> id_type = socket_type_to_id_type(
eNodeSocketDatatype(input->socket_typeinfo()->type));
if (!id_type) {
return;
}
const char *id_name = IDP_String(prop);
ID *id = BKE_libblock_find_name(&const_cast<Main &>(bmain), *id_type, id_name);
if (!id) {
return;
}
ids.add(prop->name, id);
});
return ids;
}
static Depsgraph *build_extra_depsgraph(const Depsgraph &depsgraph_active, const Set<ID *> &ids)
{
Depsgraph *depsgraph = DEG_graph_new(DEG_get_bmain(&depsgraph_active),
DEG_get_input_scene(&depsgraph_active),
DEG_get_input_view_layer(&depsgraph_active),
DEG_get_mode(&depsgraph_active));
DEG_graph_build_from_ids(depsgraph, Vector<ID *>(ids.begin(), ids.end()));
DEG_evaluate_on_refresh(depsgraph);
return depsgraph;
}
static IDProperty *replace_strings_with_id_pointers(const IDProperty &op_properties,
const Map<StringRef, ID *> &input_ids)
{
IDProperty *properties = bke::idprop::create_group("Exec Properties").release();
IDP_foreach_property(&const_cast<IDProperty &>(op_properties), 0, [&](IDProperty *prop) {
if (ID *id = input_ids.lookup_default(prop->name, nullptr)) {
IDP_AddToGroup(properties, bke::idprop::create(prop->name, id).release());
}
else {
IDP_AddToGroup(properties, IDP_CopyProperty(prop));
}
});
return properties;
}
static void replace_inputs_evaluated_data_blocks(
IDProperty &properties, const nodes::GeoNodesOperatorDepsgraphs &depsgraphs)
{
IDP_foreach_property(&properties, IDP_TYPE_FILTER_ID, [&](IDProperty *property) {
if (ID *id = IDP_Id(property)) {
if (ID_TYPE_USE_COPY_ON_EVAL(GS(id->name))) {
property->data.pointer = const_cast<ID *>(depsgraphs.get_evaluated_id(*id));
}
}
});
}
static bool object_has_editable_data(const Main &bmain, const Object &object)
{
if (!ELEM(object.type, OB_CURVES, OB_POINTCLOUD, OB_MESH)) {
return false;
}
if (!BKE_id_is_editable(&bmain, static_cast<const ID *>(object.data))) {
return false;
}
return true;
}
static Vector<Object *> gather_supported_objects(const bContext &C,
const Main &bmain,
const eObjectMode mode)
{
Vector<Object *> objects;
Set<const ID *> unique_object_data;
auto handle_object = [&](Object *object) {
if (object->mode != mode) {
return;
}
if (!unique_object_data.add(static_cast<const ID *>(object->data))) {
return;
}
if (!object_has_editable_data(bmain, *object)) {
return;
}
objects.append(object);
};
if (mode == OB_MODE_OBJECT) {
CTX_DATA_BEGIN (&C, Object *, object, selected_objects) {
handle_object(object);
}
CTX_DATA_END;
}
else {
Scene *scene = CTX_data_scene(&C);
ViewLayer *view_layer = CTX_data_view_layer(&C);
View3D *v3d = CTX_wm_view3d(&C);
Object *active_object = CTX_data_active_object(&C);
if (v3d && active_object) {
FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, active_object->type, mode, ob) {
handle_object(ob);
}
FOREACH_OBJECT_IN_MODE_END;
}
}
return objects;
}
static int run_node_group_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *active_object = CTX_data_active_object(C);
if (!active_object) {
return OPERATOR_CANCELLED;
}
const eObjectMode mode = eObjectMode(active_object->mode);
const bNodeTree *node_tree_orig = get_node_group(*C, *op->ptr, op->reports);
if (!node_tree_orig) {
return OPERATOR_CANCELLED;
}
const Vector<Object *> objects = gather_supported_objects(*C, *bmain, mode);
Depsgraph *depsgraph_active = CTX_data_ensure_evaluated_depsgraph(C);
Set<ID *> extra_ids;
gather_node_group_ids(*node_tree_orig, extra_ids);
const Map<StringRef, ID *> input_ids = gather_input_ids(
*bmain, *node_tree_orig, *op->properties);
for (ID *id : input_ids.values()) {
/* Skip IDs that are already fully evaluated in the active depsgraph. */
if (!DEG_id_is_fully_evaluated(depsgraph_active, id)) {
extra_ids.add(id);
}
}
const nodes::GeoNodesOperatorDepsgraphs depsgraphs{
depsgraph_active,
extra_ids.is_empty() ? nullptr : build_extra_depsgraph(*depsgraph_active, extra_ids),
};
IDProperty *properties = replace_strings_with_id_pointers(*op->properties, input_ids);
BLI_SCOPED_DEFER([&]() { IDP_FreeProperty_ex(properties, false); });
replace_inputs_evaluated_data_blocks(*properties, depsgraphs);
const bNodeTree *node_tree = nullptr;
if (depsgraphs.extra) {
node_tree = reinterpret_cast<const bNodeTree *>(
DEG_get_evaluated_id(depsgraphs.extra, const_cast<ID *>(&node_tree_orig->id)));
}
else {
node_tree = node_tree_orig;
}
const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
nodes::ensure_geometry_nodes_lazy_function_graph(*node_tree);
if (lf_graph_info == nullptr) {
BKE_report(op->reports, RPT_ERROR, "Cannot evaluate node group");
return OPERATOR_CANCELLED;
}
if (!node_tree->group_output_node()) {
BKE_report(op->reports, RPT_ERROR, "Node group must have a group output node");
return OPERATOR_CANCELLED;
}
if (node_tree->interface_outputs().is_empty() ||
!STREQ(node_tree->interface_outputs()[0]->socket_type, "NodeSocketGeometry"))
{
BKE_report(op->reports, RPT_ERROR, "Node group's first output must be a geometry");
return OPERATOR_CANCELLED;
}
bke::OperatorComputeContext compute_context;
Set<ComputeContextHash> socket_log_contexts;
GeoOperatorLog &eval_log = get_static_eval_log();
eval_log.log = std::make_unique<geo_log::GeoModifierLog>();
eval_log.node_group_name = node_tree->id.name + 2;
find_socket_log_contexts(*bmain, socket_log_contexts);
/* May be null if operator called from outside 3D view context. */
const RegionView3D *rv3d = CTX_wm_region_view3d(C);
Vector<MeshState> orig_mesh_states;
for (Object *object : objects) {
nodes::GeoNodesOperatorData operator_eval_data{};
operator_eval_data.mode = mode;
operator_eval_data.depsgraphs = &depsgraphs;
operator_eval_data.self_object_orig = object;
operator_eval_data.scene_orig = scene;
RNA_int_get_array(op->ptr, "mouse_position", operator_eval_data.mouse_position);
RNA_int_get_array(op->ptr, "region_size", operator_eval_data.region_size);
RNA_float_get_array(op->ptr, "cursor_position", operator_eval_data.cursor_position);
RNA_float_get_array(op->ptr, "cursor_rotation", &operator_eval_data.cursor_rotation.w);
RNA_float_get_array(
op->ptr, "viewport_projection_matrix", operator_eval_data.viewport_winmat.base_ptr());
RNA_float_get_array(
op->ptr, "viewport_view_matrix", operator_eval_data.viewport_viewmat.base_ptr());
operator_eval_data.viewport_is_perspective = RNA_boolean_get(op->ptr,
"viewport_is_perspective");
nodes::GeoNodesCallData call_data{};
call_data.operator_data = &operator_eval_data;
call_data.eval_log = eval_log.log.get();
if (object == active_object) {
/* Only log values from the active object. */
call_data.socket_log_contexts = &socket_log_contexts;
}
bke::GeometrySet geometry_orig = get_original_geometry_eval_copy(
*depsgraph_active, *object, operator_eval_data, orig_mesh_states);
bke::GeometrySet new_geometry = nodes::execute_geometry_nodes_on_geometry(
*node_tree, properties, compute_context, call_data, std::move(geometry_orig));
store_result_geometry(
*op, *depsgraph_active, *bmain, *scene, *object, rv3d, std::move(new_geometry));
WM_event_add_notifier(C, NC_GEOM | ND_DATA, object->data);
}
geo_log::GeoTreeLog &tree_log = eval_log.log->get_tree_log(compute_context.hash());
tree_log.ensure_node_warnings(node_tree);
for (const geo_log::NodeWarning &warning : tree_log.all_warnings) {
if (warning.type == geo_log::NodeWarningType::Info) {
BKE_report(op->reports, RPT_INFO, warning.message.c_str());
}
else {
BKE_report(op->reports, RPT_WARNING, warning.message.c_str());
}
}
return OPERATOR_FINISHED;
}
/**
* Input node values are stored as operator properties in order to support redoing from the redo
* panel for a few reasons:
* 1. Some data (like the mouse position) cannot be retrieved from the `exec` callback used for
* operator redo. Redo is meant to just call the operator again with the exact same properties.
* 2. While adjusting an input in the redo panel, the user doesn't expect anything else to change.
* If we retrieve other data like the viewport transform on every execution, that won't be the
* case.
* We use operator RNA properties instead of operator custom data because the custom data struct
* isn't maintained for the redo `exec` call.
*/
static void store_input_node_values_rna_props(const bContext &C,
wmOperator &op,
const wmEvent &event)
{
Scene *scene = CTX_data_scene(&C);
/* NOTE: `region` and `rv3d` may be null when called from a script. */
const ARegion *region = CTX_wm_region(&C);
const RegionView3D *rv3d = CTX_wm_region_view3d(&C);
/* Mouse position node inputs. */
RNA_int_set_array(op.ptr, "mouse_position", event.mval);
RNA_int_set_array(
op.ptr,
"region_size",
region ? int2(BLI_rcti_size_x(&region->winrct), BLI_rcti_size_y(&region->winrct)) : int2(0));
/* 3D cursor node inputs. */
const View3DCursor &cursor = scene->cursor;
RNA_float_set_array(op.ptr, "cursor_position", cursor.location);
math::Quaternion cursor_rotation = cursor.rotation();
RNA_float_set_array(op.ptr, "cursor_rotation", &cursor_rotation.w);
/* Viewport transform node inputs. */
RNA_float_set_array(op.ptr,
"viewport_projection_matrix",
rv3d ? float4x4(rv3d->winmat).base_ptr() : float4x4::identity().base_ptr());
RNA_float_set_array(op.ptr,
"viewport_view_matrix",
rv3d ? float4x4(rv3d->viewmat).base_ptr() : float4x4::identity().base_ptr());
RNA_boolean_set(op.ptr, "viewport_is_perspective", rv3d ? bool(rv3d->is_persp) : true);
}
static int run_node_group_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const bNodeTree *node_tree = get_node_group(*C, *op->ptr, op->reports);
if (!node_tree) {
return OPERATOR_CANCELLED;
}
store_input_node_values_rna_props(*C, *op, *event);
nodes ::update_input_properties_from_node_tree(
*node_tree, op->properties, *op->properties, true);
nodes::update_output_properties_from_node_tree(*node_tree, op->properties, *op->properties);
return run_node_group_exec(C, op);
}
static std::string run_node_group_get_description(bContext *C,
wmOperatorType * /*ot*/,
PointerRNA *ptr)
{
const asset_system::AssetRepresentation *asset =
asset::operator_asset_reference_props_get_asset_from_all_library(*C, *ptr, nullptr);
if (!asset) {
return "";
}
if (!asset->get_metadata().description) {
return "";
}
return asset->get_metadata().description;
}
static void add_attribute_search_or_value_buttons(uiLayout *layout,
PointerRNA *md_ptr,
const StringRef socket_id_esc,
const StringRefNull rna_path,
Nodes: Panels integration with blend files and UI Part 3/3 of #109135, #110272 Switch to new node group interfaces and deprecate old DNA and API. This completes support for panels in node drawing and in node group interface declarations in particular. The new node group interface DNA and RNA code has been added in parts 1 and 2 (#110885, #110952) but has not be enabled yet. This commit completes the integration by * enabling the new RNA API * using the new API in UI * read/write new interfaces from blend files * add versioning for backward compatibility * add forward-compatible writing code to reconstruct old interfaces All places accessing node group interface declarations should now be using the new API. A runtime cache has been added that allows simple linear access to socket inputs and outputs even when a panel hierarchy is used. Old DNA has been deprecated and should only be accessed for versioning (inputs/outputs renamed to inputs_legacy/outputs_legacy to catch errors). Versioning code ensures both backward and forward compatibility of existing files. The API for old interfaces is removed. The new API is very similar but is defined on the `ntree.interface` instead of the `ntree` directly. Breaking change notifications and detailed instructions for migrating will be added. A python test has been added for the node group API functions. This includes new functionality such as creating panels and moving items between different levels. This patch does not yet contain panel representations in the modifier UI. This has been tested in a separate branch and will be added with a later PR (#108565). Pull Request: https://projects.blender.org/blender/blender/pulls/111348
2023-08-30 12:37:21 +02:00
const bNodeTreeInterfaceSocket &socket)
{
bke::bNodeSocketType *typeinfo = bke::node_socket_type_find(socket.socket_type);
Nodes: Panels integration with blend files and UI Part 3/3 of #109135, #110272 Switch to new node group interfaces and deprecate old DNA and API. This completes support for panels in node drawing and in node group interface declarations in particular. The new node group interface DNA and RNA code has been added in parts 1 and 2 (#110885, #110952) but has not be enabled yet. This commit completes the integration by * enabling the new RNA API * using the new API in UI * read/write new interfaces from blend files * add versioning for backward compatibility * add forward-compatible writing code to reconstruct old interfaces All places accessing node group interface declarations should now be using the new API. A runtime cache has been added that allows simple linear access to socket inputs and outputs even when a panel hierarchy is used. Old DNA has been deprecated and should only be accessed for versioning (inputs/outputs renamed to inputs_legacy/outputs_legacy to catch errors). Versioning code ensures both backward and forward compatibility of existing files. The API for old interfaces is removed. The new API is very similar but is defined on the `ntree.interface` instead of the `ntree` directly. Breaking change notifications and detailed instructions for migrating will be added. A python test has been added for the node group API functions. This includes new functionality such as creating panels and moving items between different levels. This patch does not yet contain panel representations in the modifier UI. This has been tested in a separate branch and will be added with a later PR (#108565). Pull Request: https://projects.blender.org/blender/blender/pulls/111348
2023-08-30 12:37:21 +02:00
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(typeinfo->type);
const std::string rna_path_use_attribute = fmt::format(
"[\"{}{}\"]", socket_id_esc, nodes::input_use_attribute_suffix);
const std::string rna_path_attribute_name = fmt::format(
"[\"{}{}\"]", 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 bool use_attribute = RNA_boolean_get(md_ptr, rna_path_use_attribute.c_str());
Nodes: Panels integration with blend files and UI Part 3/3 of #109135, #110272 Switch to new node group interfaces and deprecate old DNA and API. This completes support for panels in node drawing and in node group interface declarations in particular. The new node group interface DNA and RNA code has been added in parts 1 and 2 (#110885, #110952) but has not be enabled yet. This commit completes the integration by * enabling the new RNA API * using the new API in UI * read/write new interfaces from blend files * add versioning for backward compatibility * add forward-compatible writing code to reconstruct old interfaces All places accessing node group interface declarations should now be using the new API. A runtime cache has been added that allows simple linear access to socket inputs and outputs even when a panel hierarchy is used. Old DNA has been deprecated and should only be accessed for versioning (inputs/outputs renamed to inputs_legacy/outputs_legacy to catch errors). Versioning code ensures both backward and forward compatibility of existing files. The API for old interfaces is removed. The new API is very similar but is defined on the `ntree.interface` instead of the `ntree` directly. Breaking change notifications and detailed instructions for migrating will be added. A python test has been added for the node group API functions. This includes new functionality such as creating panels and moving items between different levels. This patch does not yet contain panel representations in the modifier UI. This has been tested in a separate branch and will be added with a later PR (#108565). Pull Request: https://projects.blender.org/blender/blender/pulls/111348
2023-08-30 12:37:21 +02:00
if (socket_type == SOCK_BOOLEAN && !use_attribute) {
uiItemL(name_row, "", ICON_NONE);
}
else {
uiItemL(name_row, socket.name ? socket.name : "", ICON_NONE);
}
uiLayout *prop_row = uiLayoutRow(split, true);
Nodes: Panels integration with blend files and UI Part 3/3 of #109135, #110272 Switch to new node group interfaces and deprecate old DNA and API. This completes support for panels in node drawing and in node group interface declarations in particular. The new node group interface DNA and RNA code has been added in parts 1 and 2 (#110885, #110952) but has not be enabled yet. This commit completes the integration by * enabling the new RNA API * using the new API in UI * read/write new interfaces from blend files * add versioning for backward compatibility * add forward-compatible writing code to reconstruct old interfaces All places accessing node group interface declarations should now be using the new API. A runtime cache has been added that allows simple linear access to socket inputs and outputs even when a panel hierarchy is used. Old DNA has been deprecated and should only be accessed for versioning (inputs/outputs renamed to inputs_legacy/outputs_legacy to catch errors). Versioning code ensures both backward and forward compatibility of existing files. The API for old interfaces is removed. The new API is very similar but is defined on the `ntree.interface` instead of the `ntree` directly. Breaking change notifications and detailed instructions for migrating will be added. A python test has been added for the node group API functions. This includes new functionality such as creating panels and moving items between different levels. This patch does not yet contain panel representations in the modifier UI. This has been tested in a separate branch and will be added with a later PR (#108565). Pull Request: https://projects.blender.org/blender/blender/pulls/111348
2023-08-30 12:37:21 +02:00
if (socket_type == SOCK_BOOLEAN) {
uiLayoutSetPropSep(prop_row, false);
uiLayoutSetAlignment(prop_row, UI_LAYOUT_ALIGN_EXPAND);
}
if (use_attribute) {
/* TODO: Add attribute search. */
uiItemR(prop_row, md_ptr, rna_path_attribute_name, UI_ITEM_NONE, "", ICON_NONE);
}
else {
const char *name = socket_type == SOCK_BOOLEAN ? (socket.name ? socket.name : "") : "";
uiItemR(prop_row, md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
}
uiItemR(prop_row, md_ptr, rna_path_use_attribute, UI_ITEM_R_ICON_ONLY, "", ICON_SPREADSHEET);
}
static void draw_property_for_socket(const bNodeTree &node_tree,
uiLayout *layout,
IDProperty *op_properties,
PointerRNA *bmain_ptr,
PointerRNA *op_ptr,
Nodes: Panels integration with blend files and UI Part 3/3 of #109135, #110272 Switch to new node group interfaces and deprecate old DNA and API. This completes support for panels in node drawing and in node group interface declarations in particular. The new node group interface DNA and RNA code has been added in parts 1 and 2 (#110885, #110952) but has not be enabled yet. This commit completes the integration by * enabling the new RNA API * using the new API in UI * read/write new interfaces from blend files * add versioning for backward compatibility * add forward-compatible writing code to reconstruct old interfaces All places accessing node group interface declarations should now be using the new API. A runtime cache has been added that allows simple linear access to socket inputs and outputs even when a panel hierarchy is used. Old DNA has been deprecated and should only be accessed for versioning (inputs/outputs renamed to inputs_legacy/outputs_legacy to catch errors). Versioning code ensures both backward and forward compatibility of existing files. The API for old interfaces is removed. The new API is very similar but is defined on the `ntree.interface` instead of the `ntree` directly. Breaking change notifications and detailed instructions for migrating will be added. A python test has been added for the node group API functions. This includes new functionality such as creating panels and moving items between different levels. This patch does not yet contain panel representations in the modifier UI. This has been tested in a separate branch and will be added with a later PR (#108565). Pull Request: https://projects.blender.org/blender/blender/pulls/111348
2023-08-30 12:37:21 +02:00
const bNodeTreeInterfaceSocket &socket,
Nodes: automatically gray out input values that don't affect the output This patch automatically grays out input values which can't affect the output currently. It works with inputs of group nodes, geometry nodes modifiers and node tools. To achieve this, it analyses the node tree and partially evaluates it to figure out which group inputs are currently not linked to an output or are disabled by e.g. some switch node. Original proposal: https://devtalk.blender.org/t/dynamic-socket-visibility/31874 Related info in blog post: https://code.blender.org/2023/11/geometry-nodes-workshop-november-2023/#dynamic-socket-visibility Follow up task for designing a UI that allows hiding sockets: #132706 Limitations: * The inferencing does not update correctly when a socket starts being animated/driven. I haven't found a good way to invalidate the cache in a good way reliably yet. It's only a very short term problem though. It fixes itself after the next modification of the node tree and is only noticeable when animating some specific sockets such as the switch node condition. * Whether a socket is grayed out is not exposed in the Python API yet. That will be done separately. * Only a partial evaluation is done to determine if an input affects an output. There should be no cases where a socket is found to be unused when it can actually affect the output. However, there can be cases where a socket is inferenced to be used even if it is not due to some complex condition. Depending on the exact circumstances, this can either be improved or the condition in the node tree should be simplified. Pull Request: https://projects.blender.org/blender/blender/pulls/132219
2025-01-21 12:53:24 +01:00
const int socket_index,
const bool affects_output)
{
bke::bNodeSocketType *typeinfo = bke::node_socket_type_find(socket.socket_type);
Nodes: Panels integration with blend files and UI Part 3/3 of #109135, #110272 Switch to new node group interfaces and deprecate old DNA and API. This completes support for panels in node drawing and in node group interface declarations in particular. The new node group interface DNA and RNA code has been added in parts 1 and 2 (#110885, #110952) but has not be enabled yet. This commit completes the integration by * enabling the new RNA API * using the new API in UI * read/write new interfaces from blend files * add versioning for backward compatibility * add forward-compatible writing code to reconstruct old interfaces All places accessing node group interface declarations should now be using the new API. A runtime cache has been added that allows simple linear access to socket inputs and outputs even when a panel hierarchy is used. Old DNA has been deprecated and should only be accessed for versioning (inputs/outputs renamed to inputs_legacy/outputs_legacy to catch errors). Versioning code ensures both backward and forward compatibility of existing files. The API for old interfaces is removed. The new API is very similar but is defined on the `ntree.interface` instead of the `ntree` directly. Breaking change notifications and detailed instructions for migrating will be added. A python test has been added for the node group API functions. This includes new functionality such as creating panels and moving items between different levels. This patch does not yet contain panel representations in the modifier UI. This has been tested in a separate branch and will be added with a later PR (#108565). Pull Request: https://projects.blender.org/blender/blender/pulls/111348
2023-08-30 12:37:21 +02:00
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(typeinfo->type);
/* The property should be created in #MOD_nodes_update_interface with the correct type. */
IDProperty *property = IDP_GetPropertyFromGroup(op_properties, 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 || !nodes::id_property_type_matches_socket(socket, *property, true)) {
return;
}
Nodes: Panels integration with blend files and UI Part 3/3 of #109135, #110272 Switch to new node group interfaces and deprecate old DNA and API. This completes support for panels in node drawing and in node group interface declarations in particular. The new node group interface DNA and RNA code has been added in parts 1 and 2 (#110885, #110952) but has not be enabled yet. This commit completes the integration by * enabling the new RNA API * using the new API in UI * read/write new interfaces from blend files * add versioning for backward compatibility * add forward-compatible writing code to reconstruct old interfaces All places accessing node group interface declarations should now be using the new API. A runtime cache has been added that allows simple linear access to socket inputs and outputs even when a panel hierarchy is used. Old DNA has been deprecated and should only be accessed for versioning (inputs/outputs renamed to inputs_legacy/outputs_legacy to catch errors). Versioning code ensures both backward and forward compatibility of existing files. The API for old interfaces is removed. The new API is very similar but is defined on the `ntree.interface` instead of the `ntree` directly. Breaking change notifications and detailed instructions for migrating will be added. A python test has been added for the node group API functions. This includes new functionality such as creating panels and moving items between different levels. This patch does not yet contain panel representations in the modifier UI. This has been tested in a separate branch and will be added with a later PR (#108565). Pull Request: https://projects.blender.org/blender/blender/pulls/111348
2023-08-30 12:37:21 +02:00
char socket_id_esc[MAX_NAME * 2];
BLI_str_escape(socket_id_esc, socket.identifier, sizeof(socket_id_esc));
char rna_path[sizeof(socket_id_esc) + 4];
SNPRINTF(rna_path, "[\"%s\"]", socket_id_esc);
uiLayout *row = uiLayoutRow(layout, true);
Nodes: automatically gray out input values that don't affect the output This patch automatically grays out input values which can't affect the output currently. It works with inputs of group nodes, geometry nodes modifiers and node tools. To achieve this, it analyses the node tree and partially evaluates it to figure out which group inputs are currently not linked to an output or are disabled by e.g. some switch node. Original proposal: https://devtalk.blender.org/t/dynamic-socket-visibility/31874 Related info in blog post: https://code.blender.org/2023/11/geometry-nodes-workshop-november-2023/#dynamic-socket-visibility Follow up task for designing a UI that allows hiding sockets: #132706 Limitations: * The inferencing does not update correctly when a socket starts being animated/driven. I haven't found a good way to invalidate the cache in a good way reliably yet. It's only a very short term problem though. It fixes itself after the next modification of the node tree and is only noticeable when animating some specific sockets such as the switch node condition. * Whether a socket is grayed out is not exposed in the Python API yet. That will be done separately. * Only a partial evaluation is done to determine if an input affects an output. There should be no cases where a socket is found to be unused when it can actually affect the output. However, there can be cases where a socket is inferenced to be used even if it is not due to some complex condition. Depending on the exact circumstances, this can either be improved or the condition in the node tree should be simplified. Pull Request: https://projects.blender.org/blender/blender/pulls/132219
2025-01-21 12:53:24 +01:00
uiLayoutSetActive(row, affects_output);
uiLayoutSetPropDecorate(row, false);
/* 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 char *name = socket.name ? socket.name : "";
Nodes: Panels integration with blend files and UI Part 3/3 of #109135, #110272 Switch to new node group interfaces and deprecate old DNA and API. This completes support for panels in node drawing and in node group interface declarations in particular. The new node group interface DNA and RNA code has been added in parts 1 and 2 (#110885, #110952) but has not be enabled yet. This commit completes the integration by * enabling the new RNA API * using the new API in UI * read/write new interfaces from blend files * add versioning for backward compatibility * add forward-compatible writing code to reconstruct old interfaces All places accessing node group interface declarations should now be using the new API. A runtime cache has been added that allows simple linear access to socket inputs and outputs even when a panel hierarchy is used. Old DNA has been deprecated and should only be accessed for versioning (inputs/outputs renamed to inputs_legacy/outputs_legacy to catch errors). Versioning code ensures both backward and forward compatibility of existing files. The API for old interfaces is removed. The new API is very similar but is defined on the `ntree.interface` instead of the `ntree` directly. Breaking change notifications and detailed instructions for migrating will be added. A python test has been added for the node group API functions. This includes new functionality such as creating panels and moving items between different levels. This patch does not yet contain panel representations in the modifier UI. This has been tested in a separate branch and will be added with a later PR (#108565). Pull Request: https://projects.blender.org/blender/blender/pulls/111348
2023-08-30 12:37:21 +02:00
switch (socket_type) {
case SOCK_OBJECT:
uiItemPointerR(row, op_ptr, rna_path, bmain_ptr, "objects", name, ICON_OBJECT_DATA);
break;
case SOCK_COLLECTION:
uiItemPointerR(
row, op_ptr, rna_path, bmain_ptr, "collections", name, ICON_OUTLINER_COLLECTION);
break;
case SOCK_MATERIAL:
uiItemPointerR(row, op_ptr, rna_path, bmain_ptr, "materials", name, ICON_MATERIAL);
break;
case SOCK_TEXTURE:
uiItemPointerR(row, op_ptr, rna_path, bmain_ptr, "textures", name, ICON_TEXTURE);
break;
case SOCK_IMAGE:
uiItemPointerR(row, op_ptr, rna_path, bmain_ptr, "images", name, ICON_IMAGE);
break;
default:
if (nodes::input_has_attribute_toggle(node_tree, socket_index)) {
add_attribute_search_or_value_buttons(row, op_ptr, socket_id_esc, rna_path, socket);
}
else {
uiItemR(row, op_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
}
}
if (!nodes::input_has_attribute_toggle(node_tree, socket_index)) {
uiItemL(row, "", ICON_BLANK1);
}
}
static void run_node_group_ui(bContext *C, wmOperator *op)
{
uiLayout *layout = op->layout;
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
Main *bmain = CTX_data_main(C);
PointerRNA bmain_ptr = RNA_main_pointer_create(bmain);
const bNodeTree *node_tree = get_node_group(*C, *op->ptr, nullptr);
if (!node_tree) {
return;
}
node_tree->ensure_interface_cache();
Nodes: automatically gray out input values that don't affect the output This patch automatically grays out input values which can't affect the output currently. It works with inputs of group nodes, geometry nodes modifiers and node tools. To achieve this, it analyses the node tree and partially evaluates it to figure out which group inputs are currently not linked to an output or are disabled by e.g. some switch node. Original proposal: https://devtalk.blender.org/t/dynamic-socket-visibility/31874 Related info in blog post: https://code.blender.org/2023/11/geometry-nodes-workshop-november-2023/#dynamic-socket-visibility Follow up task for designing a UI that allows hiding sockets: #132706 Limitations: * The inferencing does not update correctly when a socket starts being animated/driven. I haven't found a good way to invalidate the cache in a good way reliably yet. It's only a very short term problem though. It fixes itself after the next modification of the node tree and is only noticeable when animating some specific sockets such as the switch node condition. * Whether a socket is grayed out is not exposed in the Python API yet. That will be done separately. * Only a partial evaluation is done to determine if an input affects an output. There should be no cases where a socket is found to be unused when it can actually affect the output. However, there can be cases where a socket is inferenced to be used even if it is not due to some complex condition. Depending on the exact circumstances, this can either be improved or the condition in the node tree should be simplified. Pull Request: https://projects.blender.org/blender/blender/pulls/132219
2025-01-21 12:53:24 +01:00
Array<bool> input_usages(node_tree->interface_inputs().size());
nodes::socket_usage_inference::infer_group_interface_inputs_usage(
*node_tree, op->properties, input_usages);
Nodes: Panels integration with blend files and UI Part 3/3 of #109135, #110272 Switch to new node group interfaces and deprecate old DNA and API. This completes support for panels in node drawing and in node group interface declarations in particular. The new node group interface DNA and RNA code has been added in parts 1 and 2 (#110885, #110952) but has not be enabled yet. This commit completes the integration by * enabling the new RNA API * using the new API in UI * read/write new interfaces from blend files * add versioning for backward compatibility * add forward-compatible writing code to reconstruct old interfaces All places accessing node group interface declarations should now be using the new API. A runtime cache has been added that allows simple linear access to socket inputs and outputs even when a panel hierarchy is used. Old DNA has been deprecated and should only be accessed for versioning (inputs/outputs renamed to inputs_legacy/outputs_legacy to catch errors). Versioning code ensures both backward and forward compatibility of existing files. The API for old interfaces is removed. The new API is very similar but is defined on the `ntree.interface` instead of the `ntree` directly. Breaking change notifications and detailed instructions for migrating will be added. A python test has been added for the node group API functions. This includes new functionality such as creating panels and moving items between different levels. This patch does not yet contain panel representations in the modifier UI. This has been tested in a separate branch and will be added with a later PR (#108565). Pull Request: https://projects.blender.org/blender/blender/pulls/111348
2023-08-30 12:37:21 +02:00
int input_index = 0;
for (const bNodeTreeInterfaceSocket *io_socket : node_tree->interface_inputs()) {
Nodes: automatically gray out input values that don't affect the output This patch automatically grays out input values which can't affect the output currently. It works with inputs of group nodes, geometry nodes modifiers and node tools. To achieve this, it analyses the node tree and partially evaluates it to figure out which group inputs are currently not linked to an output or are disabled by e.g. some switch node. Original proposal: https://devtalk.blender.org/t/dynamic-socket-visibility/31874 Related info in blog post: https://code.blender.org/2023/11/geometry-nodes-workshop-november-2023/#dynamic-socket-visibility Follow up task for designing a UI that allows hiding sockets: #132706 Limitations: * The inferencing does not update correctly when a socket starts being animated/driven. I haven't found a good way to invalidate the cache in a good way reliably yet. It's only a very short term problem though. It fixes itself after the next modification of the node tree and is only noticeable when animating some specific sockets such as the switch node condition. * Whether a socket is grayed out is not exposed in the Python API yet. That will be done separately. * Only a partial evaluation is done to determine if an input affects an output. There should be no cases where a socket is found to be unused when it can actually affect the output. However, there can be cases where a socket is inferenced to be used even if it is not due to some complex condition. Depending on the exact circumstances, this can either be improved or the condition in the node tree should be simplified. Pull Request: https://projects.blender.org/blender/blender/pulls/132219
2025-01-21 12:53:24 +01:00
draw_property_for_socket(*node_tree,
layout,
op->properties,
&bmain_ptr,
op->ptr,
*io_socket,
input_index,
input_usages[input_index]);
Nodes: Panels integration with blend files and UI Part 3/3 of #109135, #110272 Switch to new node group interfaces and deprecate old DNA and API. This completes support for panels in node drawing and in node group interface declarations in particular. The new node group interface DNA and RNA code has been added in parts 1 and 2 (#110885, #110952) but has not be enabled yet. This commit completes the integration by * enabling the new RNA API * using the new API in UI * read/write new interfaces from blend files * add versioning for backward compatibility * add forward-compatible writing code to reconstruct old interfaces All places accessing node group interface declarations should now be using the new API. A runtime cache has been added that allows simple linear access to socket inputs and outputs even when a panel hierarchy is used. Old DNA has been deprecated and should only be accessed for versioning (inputs/outputs renamed to inputs_legacy/outputs_legacy to catch errors). Versioning code ensures both backward and forward compatibility of existing files. The API for old interfaces is removed. The new API is very similar but is defined on the `ntree.interface` instead of the `ntree` directly. Breaking change notifications and detailed instructions for migrating will be added. A python test has been added for the node group API functions. This includes new functionality such as creating panels and moving items between different levels. This patch does not yet contain panel representations in the modifier UI. This has been tested in a separate branch and will be added with a later PR (#108565). Pull Request: https://projects.blender.org/blender/blender/pulls/111348
2023-08-30 12:37:21 +02:00
++input_index;
}
}
static bool run_node_ui_poll(wmOperatorType * /*ot*/, PointerRNA *ptr)
{
bool result = false;
RNA_STRUCT_BEGIN (ptr, prop) {
int flag = RNA_property_flag(prop);
if ((flag & PROP_HIDDEN) == 0) {
result = true;
break;
}
}
RNA_STRUCT_END;
return result;
}
static std::string run_node_group_get_name(wmOperatorType * /*ot*/, PointerRNA *ptr)
{
int len;
char *local_name = RNA_string_get_alloc(ptr, "name", nullptr, 0, &len);
BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(local_name); })
if (len > 0) {
return std::string(local_name, len);
}
char *library_asset_identifier = RNA_string_get_alloc(
ptr, "relative_asset_identifier", nullptr, 0, &len);
BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(library_asset_identifier); })
StringRef ref(library_asset_identifier, len);
return ref.drop_prefix(ref.find_last_of(SEP_STR) + 1);
}
static bool run_node_group_depends_on_cursor(bContext &C, wmOperatorType & /*ot*/, PointerRNA *ptr)
{
if (!ptr) {
return false;
}
Main &bmain = *CTX_data_main(&C);
if (bNodeTree *group = reinterpret_cast<bNodeTree *>(
WM_operator_properties_id_lookup_from_name_or_session_uid(&bmain, ptr, ID_NT)))
{
return group->geometry_node_asset_traits &&
(group->geometry_node_asset_traits->flag & GEO_NODE_ASSET_WAIT_FOR_CURSOR) != 0;
}
const asset_system::AssetRepresentation *asset =
asset::operator_asset_reference_props_get_asset_from_all_library(C, *ptr, nullptr);
if (!asset) {
return false;
}
const IDProperty *traits_flag = BKE_asset_metadata_idprop_find(
&asset->get_metadata(), "geometry_node_asset_traits_flag");
if (traits_flag == nullptr || !(IDP_Int(traits_flag) & GEO_NODE_ASSET_WAIT_FOR_CURSOR)) {
return false;
}
return true;
}
void GEOMETRY_OT_execute_node_group(wmOperatorType *ot)
{
PropertyRNA *prop;
ot->name = "Run Node Group";
ot->idname = __func__;
ot->description = "Execute a node group on geometry";
/* A proper poll is not possible, since it doesn't have access to the operator's properties. */
ot->invoke = run_node_group_invoke;
ot->exec = run_node_group_exec;
ot->get_description = run_node_group_get_description;
ot->ui = run_node_group_ui;
ot->ui_poll = run_node_ui_poll;
ot->get_name = run_node_group_get_name;
ot->depends_on_cursor = run_node_group_depends_on_cursor;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
asset::operator_asset_reference_props_register(*ot->srna);
WM_operator_properties_id_lookup(ot, true);
/* See comment for #store_input_node_values_rna_props. */
prop = RNA_def_int_array(ot->srna,
"mouse_position",
2,
nullptr,
INT_MIN,
INT_MAX,
"Mouse Position",
"Mouse coordinates in region space",
INT_MIN,
INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN);
prop = RNA_def_int_array(
ot->srna, "region_size", 2, nullptr, 0, INT_MAX, "Region Size", "", 0, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN);
prop = RNA_def_float_array(ot->srna,
"cursor_position",
3,
nullptr,
FLT_MIN,
FLT_MAX,
"3D Cursor Position",
"",
FLT_MIN,
FLT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN);
prop = RNA_def_float_array(ot->srna,
"cursor_rotation",
4,
nullptr,
FLT_MIN,
FLT_MAX,
"3D Cursor Rotation",
"",
FLT_MIN,
FLT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN);
prop = RNA_def_float_array(ot->srna,
"viewport_projection_matrix",
16,
nullptr,
FLT_MIN,
FLT_MAX,
"Viewport Projection Transform",
"",
FLT_MIN,
FLT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN);
prop = RNA_def_float_array(ot->srna,
"viewport_view_matrix",
16,
nullptr,
FLT_MIN,
FLT_MAX,
"Viewport View Transform",
"",
FLT_MIN,
FLT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN);
prop = RNA_def_boolean(
ot->srna, "viewport_is_perspective", false, "Viewport Is Perspective", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Menu
* \{ */
static bool asset_menu_poll(const bContext *C, MenuType * /*mt*/)
{
return CTX_wm_view3d(C);
}
static GeometryNodeAssetTraitFlag asset_flag_for_context(const ObjectType type,
const eObjectMode mode)
{
switch (type) {
case OB_MESH: {
switch (mode) {
case OB_MODE_OBJECT:
return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_OBJECT | GEO_NODE_ASSET_MESH);
case OB_MODE_EDIT:
return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_EDIT | GEO_NODE_ASSET_MESH);
case OB_MODE_SCULPT:
return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_SCULPT | GEO_NODE_ASSET_MESH);
default:
break;
}
break;
}
case OB_CURVES: {
switch (mode) {
case OB_MODE_OBJECT:
return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_OBJECT | GEO_NODE_ASSET_CURVE);
case OB_MODE_EDIT:
return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_EDIT | GEO_NODE_ASSET_CURVE);
case OB_MODE_SCULPT_CURVES:
return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_SCULPT | GEO_NODE_ASSET_CURVE);
default:
break;
}
break;
}
case OB_POINTCLOUD: {
switch (mode) {
case OB_MODE_OBJECT:
return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_OBJECT | GEO_NODE_ASSET_POINTCLOUD);
case OB_MODE_EDIT:
return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_EDIT | GEO_NODE_ASSET_POINTCLOUD);
default:
break;
}
break;
}
default:
break;
}
BLI_assert_unreachable();
return GeometryNodeAssetTraitFlag(0);
}
static GeometryNodeAssetTraitFlag asset_flag_for_context(const Object &active_object)
{
return asset_flag_for_context(ObjectType(active_object.type), eObjectMode(active_object.mode));
}
static asset::AssetItemTree *get_static_item_tree(const ObjectType type, const eObjectMode mode)
{
switch (type) {
case OB_MESH: {
switch (mode) {
case OB_MODE_OBJECT: {
static asset::AssetItemTree tree;
return &tree;
}
case OB_MODE_EDIT: {
static asset::AssetItemTree tree;
return &tree;
}
case OB_MODE_SCULPT: {
static asset::AssetItemTree tree;
return &tree;
}
default:
return nullptr;
}
}
case OB_CURVES: {
switch (mode) {
case OB_MODE_OBJECT: {
static asset::AssetItemTree tree;
return &tree;
}
case OB_MODE_EDIT: {
static asset::AssetItemTree tree;
return &tree;
}
case OB_MODE_SCULPT_CURVES: {
static asset::AssetItemTree tree;
return &tree;
}
default:
return nullptr;
}
}
case OB_POINTCLOUD: {
switch (mode) {
case OB_MODE_OBJECT: {
static asset::AssetItemTree tree;
return &tree;
}
case OB_MODE_EDIT: {
static asset::AssetItemTree tree;
return &tree;
}
default:
return nullptr;
}
}
default:
return nullptr;
}
}
static asset::AssetItemTree *get_static_item_tree(const Object &active_object)
{
return get_static_item_tree(ObjectType(active_object.type), eObjectMode(active_object.mode));
}
void clear_operator_asset_trees()
{
for (const ObjectType type : {OB_MESH, OB_CURVES, OB_POINTCLOUD}) {
for (const eObjectMode mode :
{OB_MODE_OBJECT, OB_MODE_EDIT, OB_MODE_SCULPT, OB_MODE_SCULPT_CURVES})
{
if (asset::AssetItemTree *tree = get_static_item_tree(type, mode)) {
tree->dirty = true;
}
}
}
}
static asset::AssetItemTree build_catalog_tree(const bContext &C, const Object &active_object)
{
asset::AssetFilterSettings type_filter{};
type_filter.id_types = FILTER_ID_NT;
const GeometryNodeAssetTraitFlag flag = asset_flag_for_context(active_object);
auto meta_data_filter = [&](const AssetMetaData &meta_data) {
const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&meta_data, "type");
if (tree_type == nullptr || IDP_Int(tree_type) != NTREE_GEOMETRY) {
return false;
}
const IDProperty *traits_flag = BKE_asset_metadata_idprop_find(
&meta_data, "geometry_node_asset_traits_flag");
if (traits_flag == nullptr || (IDP_Int(traits_flag) & flag) != flag) {
return false;
}
return true;
};
const AssetLibraryReference library = asset_system::all_library_reference();
asset_system::all_library_reload_catalogs_if_dirty();
return asset::build_filtered_all_catalog_tree(library, C, type_filter, meta_data_filter);
}
/**
* Avoid adding a separate root catalog when the assets have already been added to one of the
* builtin menus. The need to define the builtin menu labels here is non-ideal. We don't have
* any UI introspection that can do this though.
*/
static Set<std::string> get_builtin_menus(const ObjectType object_type, const eObjectMode mode)
{
Set<std::string> menus;
switch (object_type) {
case OB_CURVES:
menus.add_new("View");
menus.add_new("Select");
menus.add_new("Curves");
break;
case OB_POINTCLOUD:
menus.add_new("View");
menus.add_new("Select");
menus.add_new("Point Cloud");
break;
case OB_MESH:
switch (mode) {
case OB_MODE_OBJECT:
menus.add_new("View");
menus.add_new("Select");
menus.add_new("Add");
menus.add_new("Object");
menus.add_new("Object/Apply");
menus.add_new("Object/Convert");
menus.add_new("Object/Quick Effects");
break;
case OB_MODE_EDIT:
menus.add_new("View");
menus.add_new("Select");
menus.add_new("Add");
menus.add_new("Mesh");
menus.add_new("Mesh/Extrude");
menus.add_new("Mesh/Clean Up");
menus.add_new("Mesh/Delete");
menus.add_new("Mesh/Merge");
menus.add_new("Mesh/Normals");
menus.add_new("Mesh/Shading");
menus.add_new("Mesh/Split");
menus.add_new("Mesh/Weights");
menus.add_new("Vertex");
menus.add_new("Edge");
menus.add_new("Face");
menus.add_new("Face/Face Data");
menus.add_new("UV");
menus.add_new("UV/Unwrap");
break;
case OB_MODE_SCULPT:
menus.add_new("View");
menus.add_new("Sculpt");
menus.add_new("Mask");
menus.add_new("Face Sets");
break;
case OB_MODE_VERTEX_PAINT:
menus.add_new("View");
menus.add_new("Paint");
break;
case OB_MODE_WEIGHT_PAINT:
menus.add_new("View");
menus.add_new("Weights");
break;
default:
break;
}
default:
break;
}
return menus;
}
static void catalog_assets_draw(const bContext *C, Menu *menu)
{
const Object *active_object = CTX_data_active_object(C);
if (!active_object) {
return;
}
asset::AssetItemTree *tree = get_static_item_tree(*active_object);
if (!tree) {
return;
}
const std::optional<StringRefNull> menu_path = CTX_data_string_get(C, "asset_catalog_path");
if (!menu_path) {
return;
}
const Span<asset_system::AssetRepresentation *> assets = tree->assets_per_path.lookup(
menu_path->data());
const asset_system::AssetCatalogTreeItem *catalog_item = tree->catalogs.find_item(
menu_path->data());
BLI_assert(catalog_item != nullptr);
uiLayout *layout = menu->layout;
bool add_separator = true;
wmOperatorType *ot = WM_operatortype_find("GEOMETRY_OT_execute_node_group", true);
for (const asset_system::AssetRepresentation *asset : assets) {
if (add_separator) {
uiItemS(layout);
add_separator = false;
}
PointerRNA props_ptr;
uiItemFullO_ptr(layout,
ot,
IFACE_(asset->get_name()),
ICON_NONE,
nullptr,
WM_OP_INVOKE_REGION_WIN,
UI_ITEM_NONE,
&props_ptr);
asset::operator_asset_reference_props_set(*asset, props_ptr);
}
const Set<std::string> builtin_menus = get_builtin_menus(ObjectType(active_object->type),
eObjectMode(active_object->mode));
asset_system::AssetLibrary *all_library = asset::list::library_get_once_available(
asset_system::all_library_reference());
if (!all_library) {
return;
}
catalog_item->foreach_child([&](const asset_system::AssetCatalogTreeItem &item) {
if (builtin_menus.contains_as(item.catalog_path().str())) {
return;
}
if (add_separator) {
uiItemS(layout);
add_separator = false;
}
asset::draw_menu_for_catalog(item, "GEO_MT_node_operator_catalog_assets", *layout);
});
}
MenuType node_group_operator_assets_menu()
{
MenuType type{};
STRNCPY(type.idname, "GEO_MT_node_operator_catalog_assets");
type.poll = asset_menu_poll;
type.draw = catalog_assets_draw;
type.listener = asset::list::asset_reading_region_listen_fn;
type.flag = MenuTypeFlag::ContextDependent;
return type;
}
static bool unassigned_local_poll(const bContext &C)
{
Main &bmain = *CTX_data_main(&C);
const Object *active_object = CTX_data_active_object(&C);
if (!active_object) {
return false;
}
const GeometryNodeAssetTraitFlag flag = asset_flag_for_context(*active_object);
LISTBASE_FOREACH (const bNodeTree *, group, &bmain.nodetrees) {
/* Assets are displayed in other menus, and non-local data-blocks aren't added to this menu. */
if (group->id.library_weak_reference || group->id.asset_data) {
continue;
}
if (!group->geometry_node_asset_traits ||
(group->geometry_node_asset_traits->flag & flag) != flag)
{
continue;
}
return true;
}
return false;
}
static void catalog_assets_draw_unassigned(const bContext *C, Menu *menu)
{
const Object *active_object = CTX_data_active_object(C);
if (!active_object) {
return;
}
asset::AssetItemTree *tree = get_static_item_tree(*active_object);
if (!tree) {
return;
}
uiLayout *layout = menu->layout;
wmOperatorType *ot = WM_operatortype_find("GEOMETRY_OT_execute_node_group", true);
for (const asset_system::AssetRepresentation *asset : tree->unassigned_assets) {
PointerRNA props_ptr;
uiItemFullO_ptr(layout,
ot,
IFACE_(asset->get_name()),
ICON_NONE,
nullptr,
WM_OP_INVOKE_REGION_WIN,
UI_ITEM_NONE,
&props_ptr);
asset::operator_asset_reference_props_set(*asset, props_ptr);
}
const GeometryNodeAssetTraitFlag flag = asset_flag_for_context(*active_object);
bool first = true;
bool add_separator = !tree->unassigned_assets.is_empty();
Main &bmain = *CTX_data_main(C);
LISTBASE_FOREACH (const bNodeTree *, group, &bmain.nodetrees) {
/* Assets are displayed in other menus, and non-local data-blocks aren't added to this menu. */
if (group->id.library_weak_reference || group->id.asset_data) {
continue;
}
if (!group->geometry_node_asset_traits ||
(group->geometry_node_asset_traits->flag & flag) != flag)
{
continue;
}
if (add_separator) {
uiItemS(layout);
add_separator = false;
}
if (first) {
uiItemL(layout, IFACE_("Non-Assets"), ICON_NONE);
first = false;
}
PointerRNA props_ptr;
uiItemFullO_ptr(layout,
ot,
group->id.name + 2,
ICON_NONE,
nullptr,
WM_OP_INVOKE_REGION_WIN,
UI_ITEM_NONE,
&props_ptr);
WM_operator_properties_id_lookup_set_from_id(&props_ptr, &group->id);
/* Also set the name so it can be used for #run_node_group_get_name. */
RNA_string_set(&props_ptr, "name", group->id.name + 2);
}
}
MenuType node_group_operator_assets_menu_unassigned()
{
MenuType type{};
STRNCPY(type.label, "Unassigned Node Tools");
STRNCPY(type.idname, "GEO_MT_node_operator_unassigned");
type.poll = asset_menu_poll;
type.draw = catalog_assets_draw_unassigned;
type.listener = asset::list::asset_reading_region_listen_fn;
type.flag = MenuTypeFlag::ContextDependent;
type.description = N_(
"Tool node group assets not assigned to a catalog.\n"
2023-09-22 11:16:24 -04:00
"Catalogs can be assigned in the Asset Browser");
return type;
}
void ui_template_node_operator_asset_menu_items(uiLayout &layout,
const bContext &C,
const StringRef catalog_path)
{
const Object *active_object = CTX_data_active_object(&C);
if (!active_object) {
return;
}
asset::AssetItemTree *tree = get_static_item_tree(*active_object);
if (!tree) {
return;
}
const asset_system::AssetCatalogTreeItem *item = tree->catalogs.find_item(catalog_path);
if (!item) {
return;
}
asset_system::AssetLibrary *all_library = asset::list::library_get_once_available(
asset_system::all_library_reference());
if (!all_library) {
return;
}
uiLayout *col = uiLayoutColumn(&layout, false);
uiLayoutSetContextString(col, "asset_catalog_path", item->catalog_path().str());
uiItemMContents(col, "GEO_MT_node_operator_catalog_assets");
}
void ui_template_node_operator_asset_root_items(uiLayout &layout, const bContext &C)
{
const Object *active_object = CTX_data_active_object(&C);
if (!active_object) {
return;
}
asset::AssetItemTree *tree = get_static_item_tree(*active_object);
if (!tree) {
return;
}
if (tree->dirty) {
*tree = build_catalog_tree(C, *active_object);
}
const Set<std::string> builtin_menus = get_builtin_menus(ObjectType(active_object->type),
eObjectMode(active_object->mode));
tree->catalogs.foreach_root_item([&](const asset_system::AssetCatalogTreeItem &item) {
if (!builtin_menus.contains_as(item.catalog_path().str())) {
asset::draw_menu_for_catalog(item, "GEO_MT_node_operator_catalog_assets", layout);
}
});
if (!tree->unassigned_assets.is_empty() || unassigned_local_poll(C)) {
uiItemM(&layout, "GEO_MT_node_operator_unassigned", "", ICON_FILE_HIDDEN);
}
}
/** \} */
} // namespace blender::ed::geometry