Node Tools: Add mouse position node, wait for cursor option #121043
@ -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")
|
||||
|
@ -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
|
||||
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,
|
||||
|
@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
|
@ -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
Jacques Lucke
commented
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).
Hans Goudey
commented
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
Jacques Lucke
commented
I'm more talking about I'm more talking about `region->sizex`, not sure if `region` can be null.
Hans Goudey
commented
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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
@ -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`. */
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
Jacques Lucke
commented
All these sockets need descriptions. 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
Jacques Lucke
commented
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.
Hans Goudey
commented
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
|
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:
I could just move it to an options popover already?
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.