Skip to content

Commit

Permalink
Node Tools: Add mouse position node, wait for cursor option
Browse files Browse the repository at this point in the history
Add a "Mouse Position" node that outputs the location of the mouse
cursor in region space and the overall size of the region, both in pixel
units. Both outputs are integers to help reflect their pixel units.
If there the mouse position is ever accessible in sub-pixels, they can
easily be changed to floats.

Also add a "Wait for Cursor" option similar to the one for some built-in
operators that delay's the operator's execution until there is a mouse
click in the viewport. That way the operator can be called from menus
even though it is interactive. 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.

Combined with the viewport transform node, and other nodes like Raycast,
these features can allow making tools that create geometry where you
click in the scene.

Pull Request: https://projects.blender.org/blender/blender/pulls/121043
  • Loading branch information
HansGoudey authored and Hans Goudey committed Apr 26, 2024
1 parent b306b3b commit ce224fe
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 2 deletions.
2 changes: 2 additions & 0 deletions scripts/startup/bl_ui/node_add_menu_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ def draw(self, context):
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")
Expand Down
17 changes: 17 additions & 0 deletions scripts/startup/bl_ui/space_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ def draw(self, context):
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")
display_pin = False
else:
# Custom node tree is edited as independent ID block
Expand Down Expand Up @@ -490,6 +491,21 @@ def draw(self, context):
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"
Expand Down Expand Up @@ -1320,6 +1336,7 @@ def node_panel(cls):
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,
Expand Down
1 change: 1 addition & 0 deletions source/blender/blenkernel/BKE_node.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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

/** \} */

Expand Down
51 changes: 50 additions & 1 deletion source/blender/editors/geometry/node_group_operator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
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{};
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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";
Expand All @@ -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);
}

/** \} */
Expand Down
3 changes: 2 additions & 1 deletion source/blender/makesdna/DNA_node_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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`. */

Expand Down
20 changes: 20 additions & 0 deletions source/blender/makesrna/intern/rna_nodetree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions source/blender/nodes/NOD_geometry_nodes_lazy_function.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down
1 change: 1 addition & 0 deletions source/blender/nodes/NOD_static_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions source/blender/nodes/geometry/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions source/blender/nodes/geometry/nodes/node_geo_mouse_position.cc
Original file line number Diff line number Diff line change
@@ -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(
"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");
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

0 comments on commit ce224fe

Please sign in to comment.