Node Tools: Add mouse position node, wait for cursor option #121043

Merged
Hans Goudey merged 9 commits from HooglyBoogly/blender:node-tools-mouse-position into main 2024-04-26 20:14:36 +02:00
10 changed files with 139 additions and 2 deletions

View File

@ -292,6 +292,8 @@ class NODE_MT_geometry_node_GEO_INPUT_SCENE(Menu):
node_add_menu.add_node_type(layout, "GeometryNodeIsViewport")
if context.preferences.experimental.use_grease_pencil_version3:
node_add_menu.add_node_type(layout, "GeometryNodeInputNamedLayerSelection")
if context.space_data.geometry_nodes_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeToolMousePosition")
node_add_menu.add_node_type(layout, "GeometryNodeObjectInfo")
node_add_menu.add_node_type(layout, "GeometryNodeInputSceneTime")
node_add_menu.add_node_type(layout, "GeometryNodeSelfObject")

View File

@ -171,6 +171,7 @@ class NODE_HT_header(Header):
if snode.node_tree:
layout.popover(panel="NODE_PT_geometry_node_tool_object_types", text="Types")
layout.popover(panel="NODE_PT_geometry_node_tool_mode", text="Modes")
layout.popover(panel="NODE_PT_geometry_node_tool_options", text="Options")
HooglyBoogly marked this conversation as resolved Outdated

I think when we talked about this last time, we said that this option should be displayed this prominently. It's kind of unusual to have such a checkbox in a header.

Related meeting notes: https://devtalk.blender.org/t/2024-03-19-nodes-physics-module-meeting/33867
Also note that we mentioned it should be called e.g. "Wait for Click" instead of "Wait for Cursor".

I think when we talked about this last time, we said that this option should be displayed this prominently. It's kind of unusual to have such a checkbox in a header. Related meeting notes: https://devtalk.blender.org/t/2024-03-19-nodes-physics-module-meeting/33867 Also note that we mentioned it should be called e.g. "Wait for Click" instead of "Wait for Cursor".

I mentioned this in the description:

This option is placed in the node editor header. When there are more options, it will be part of an "Options" popover panel similar to the existing "Modes" and "types" popovers.

I could just move it to an options popover already?

Also note that we mentioned it should be called e.g. "Wait for Click" instead of "Wait for Cursor".

I started with that here, but then I thought "Click" sounded wrong for pen or touch input. Not sure.. I'll ask in the UI module to see if others have ideas.

I mentioned this in the description: >This option is placed in the node editor header. When there are more options, it will be part of an "Options" popover panel similar to the existing "Modes" and "types" popovers. I could just move it to an options popover already? > Also note that we mentioned it should be called e.g. "Wait for Click" instead of "Wait for Cursor". I started with that here, but then I thought "Click" sounded wrong for pen or touch input. Not sure.. I'll ask in the UI module to see if others have ideas.

Actually I just renamed it to "Wait for Click". It can always be renamed in the future, and it seems more consistent with existing terminology than I thought.

Actually I just renamed it to "Wait for Click". It can always be renamed in the future, and it seems more consistent with existing terminology than I thought.
display_pin = False
else:
# Custom node tree is edited as independent ID block
@ -490,6 +491,21 @@ class NODE_PT_geometry_node_tool_mode(Panel):
row.prop(group, prop, text="")
class NODE_PT_geometry_node_tool_options(Panel):
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'HEADER'
bl_label = "Options"
bl_ui_units_x = 8
def draw(self, context):
layout = self.layout
snode = context.space_data
group = snode.node_tree
layout.prop(group, "use_wait_for_click")
class NODE_PT_node_color_presets(PresetPanel, Panel):
"""Predefined node color"""
bl_label = "Color Presets"
@ -1320,6 +1336,7 @@ classes = (
NODE_PT_material_slots,
NODE_PT_geometry_node_tool_object_types,
NODE_PT_geometry_node_tool_mode,
NODE_PT_geometry_node_tool_options,
NODE_PT_node_color_presets,
NODE_MT_node_tree_interface_context_menu,
NODE_PT_node_tree_interface,

View File

@ -1279,6 +1279,7 @@ void BKE_nodetree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index
#define GEO_NODE_DISTRIBUTE_POINTS_IN_GRID 2130
#define GEO_NODE_SDF_GRID_BOOLEAN 2131
#define GEO_NODE_TOOL_VIEWPORT_TRANSFORM 2132
#define GEO_NODE_TOOL_MOUSE_POSITION 2133
/** \} */

View File

@ -394,6 +394,8 @@ 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);
/* 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);
if (!active_object) {
return OPERATOR_CANCELLED;
@ -471,6 +473,8 @@ static int run_node_group_exec(bContext *C, wmOperator *op)
operator_eval_data.depsgraphs = &depsgraphs;
operator_eval_data.self_object_orig = object;
operator_eval_data.scene_orig = scene;
HooglyBoogly marked this conversation as resolved Outdated

Would be good to check if this still works if the operator is run without ui (as part of a script). Obviously, we can't get a meaningful mouse position and region size in this case, but the operator should still work I think (especially if those inputs are not used in the node group).

Would be good to check if this still works if the operator is run without ui (as part of a script). Obviously, we can't get a meaningful mouse position and region size in this case, but the operator should still work I think (especially if those inputs are not used in the node group).

I don't see why this wouldn't work-- it would just get the default RNA property value which would be 0,0

I don't see why this wouldn't work-- it would just get the default RNA property value which would be 0,0

I'm more talking about region->sizex, not sure if region can be null.

I'm more talking about `region->sizex`, not sure if `region` can be null.

Oh indeed, thanks! I think that's possible in scripts. I'll add a null check and output 0 in that case too.

Oh indeed, thanks! I think that's possible in scripts. I'll add a null check and output 0 in that case too.
RNA_int_get_array(op->ptr, "mouse_position", operator_eval_data.mouse_position);
operator_eval_data.region_size = region ? int2(region->sizex, region->sizey) : int2(0);
operator_eval_data.rv3d = rv3d;
nodes::GeoNodesCallData call_data{};
@ -506,13 +510,15 @@ static int run_node_group_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static int run_node_group_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
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;
}
RNA_int_set_array(op->ptr, "mouse_position", event->mval);
nodes::update_input_properties_from_node_tree(*node_tree, op->properties, *op->properties);
nodes::update_output_properties_from_node_tree(*node_tree, op->properties, *op->properties);
@ -697,8 +703,35 @@ static std::string run_node_group_get_name(wmOperatorType * /*ot*/, PointerRNA *
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";
@ -710,11 +743,27 @@ void GEOMETRY_OT_execute_node_group(wmOperatorType *ot)
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);
/* Store the mouse position in an RNA property rather than allocated operator custom data in
* order to support redoing the operator. Because redo uses `exec`, the mouse position will be in
* the same position in screen space. */
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);
}
/** \} */

View File

@ -960,8 +960,9 @@ typedef enum GeometryNodeAssetTraitFlag {
GEO_NODE_ASSET_POINT_CLOUD = (1 << 5),
GEO_NODE_ASSET_MODIFIER = (1 << 6),
GEO_NODE_ASSET_OBJECT = (1 << 7),
GEO_NODE_ASSET_WAIT_FOR_CURSOR = (1 << 8),
} GeometryNodeAssetTraitFlag;
ENUM_OPERATORS(GeometryNodeAssetTraitFlag, GEO_NODE_ASSET_OBJECT);
ENUM_OPERATORS(GeometryNodeAssetTraitFlag, GEO_NODE_ASSET_WAIT_FOR_CURSOR);
/* Data structs, for `node->storage`. */

View File

@ -1846,6 +1846,15 @@ static void rna_GeometryNodeTree_is_type_point_cloud_set(PointerRNA *ptr, bool v
geometry_node_asset_trait_flag_set(ptr, GEO_NODE_ASSET_POINT_CLOUD, value);
}
static bool rna_GeometryNodeTree_use_wait_for_click_get(PointerRNA *ptr)
{
return geometry_node_asset_trait_flag_get(ptr, GEO_NODE_ASSET_WAIT_FOR_CURSOR);
}
static void rna_GeometryNodeTree_use_wait_for_click_set(PointerRNA *ptr, bool value)
{
geometry_node_asset_trait_flag_set(ptr, GEO_NODE_ASSET_WAIT_FOR_CURSOR, value);
}
static bool random_value_type_supported(const EnumPropertyItem *item)
{
return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3, CD_PROP_BOOL, CD_PROP_INT32);
@ -10763,6 +10772,17 @@ static void rna_def_geometry_nodetree(BlenderRNA *brna)
"rna_GeometryNodeTree_is_type_point_cloud_get",
"rna_GeometryNodeTree_is_type_point_cloud_set");
RNA_def_property_update(prop, NC_NODE | ND_DISPLAY, "rna_NodeTree_update_asset");
prop = RNA_def_property(srna, "use_wait_for_click", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", GEO_NODE_ASSET_POINT_CLOUD);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop,
"Wait for Click",
"Wait for mouse click input before running the operator from a menu");
RNA_def_property_boolean_funcs(prop,
"rna_GeometryNodeTree_use_wait_for_click_get",
"rna_GeometryNodeTree_use_wait_for_click_set");
RNA_def_property_update(prop, NC_NODE | ND_DISPLAY, "rna_NodeTree_update_asset");
}
static StructRNA *define_specific_node(BlenderRNA *brna,

View File

@ -190,6 +190,8 @@ struct GeoNodesOperatorData {
const Object *self_object_orig = nullptr;
const GeoNodesOperatorDepsgraphs *depsgraphs = nullptr;
Scene *scene_orig = nullptr;
int2 mouse_position;
int2 region_size;
const RegionView3D *rv3d = nullptr;
};

View File

@ -466,6 +466,7 @@ DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, 0, "SUBDIVISION_SURFACE",Sub
DefNode(GeometryNode, GEO_NODE_SWITCH, 0, "SWITCH", Switch, "Switch", "Switch between two inputs")
DefNode(GeometryNode, GEO_NODE_TOOL_3D_CURSOR, 0, "TOOL_3D_CURSOR", Tool3DCursor, "3D Cursor", "The scene's 3D cursor location and rotation")
DefNode(GeometryNode, GEO_NODE_TOOL_FACE_SET, 0, "TOOL_FACE_SET", ToolFaceSet, "Face Set", "Each face's sculpt face set value")
DefNode(GeometryNode, GEO_NODE_TOOL_MOUSE_POSITION, 0, "TOOL_MOUSE_POSITION", ToolMousePosition, "Mouse Position", "Retrieve the position of the mouse cursor")
DefNode(GeometryNode, GEO_NODE_TOOL_SELECTION, 0, "TOOL_SELECTION", ToolSelection, "Selection", "User selection of the edited geometry, for tool execution")
DefNode(GeometryNode, GEO_NODE_TOOL_SET_FACE_SET, 0, "TOOL_SET_FACE_SET", ToolSetFaceSet, "Set Face Set", "Set sculpt face set values for faces")
DefNode(GeometryNode, GEO_NODE_TOOL_SET_SELECTION, 0, "TOOL_SELECTION_SET", ToolSetSelection, "Set Selection", "Set selection of the edited geometry, for tool execution")

View File

@ -149,6 +149,7 @@ set(SRC
nodes/node_geo_mesh_topology_face_of_corner.cc
nodes/node_geo_mesh_topology_offset_corner_in_face.cc
nodes/node_geo_mesh_topology_vertex_of_corner.cc
nodes/node_geo_mouse_position.cc
nodes/node_geo_object_info.cc
nodes/node_geo_offset_point_in_curve.cc
nodes/node_geo_points.cc

View File

@ -0,0 +1,43 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_mouse_position_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Int>("Mouse X").description(
HooglyBoogly marked this conversation as resolved Outdated

All these sockets need descriptions.
For Y it's especially important because it's not obvious if 0 is at the top or bottom.

All these sockets need descriptions. For Y it's especially important because it's not obvious if 0 is at the top or bottom.
"The region-space mouse X location, in pixels, increasing from 0 at the left");
b.add_output<decl::Int>("Mouse Y").description(
"The region-space mouse Y location, in pixels, increasing from 0 at the bottom");
b.add_output<decl::Int>("Region Width").description("The total X size of the region in pixels");
JacquesLucke marked this conversation as resolved
Review

Not sure if it's better, but maybe the descriptions should use "width" and "height" too.

Not sure if it's better, but maybe the descriptions should use "width" and "height" too.
Review

It does sound a bit better that way, but I think it feels a little better to follow the typical advice of "don't repeat the name in the description"

It does sound a bit better that way, but I think it feels a little better to follow the typical advice of "don't repeat the name in the description"
b.add_output<decl::Int>("Region Height").description("The total Y size of the region in pixels");
}
static void node_geo_exec(GeoNodeExecParams params)
{
if (!check_tool_context_and_error(params)) {
return;
}
const int2 mouse = params.user_data()->call_data->operator_data->mouse_position;
const int2 size = params.user_data()->call_data->operator_data->region_size;
params.set_output("Mouse X", mouse.x);
params.set_output("Mouse Y", mouse.y);
params.set_output("Region Width", size.x);
params.set_output("Region Height", size.y);
}
static void node_register()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_TOOL_MOUSE_POSITION, "Mouse Position", NODE_CLASS_INPUT);
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.gather_link_search_ops = search_link_ops_for_tool_node;
nodeRegisterType(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_mouse_position_cc