Tool script in 3.0

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Smellyhobo101
:warning: Old Version Published before Godot 3 was released.

I’m trying to make a tool script in 3.0 with the following requirements:

  1. Parts of the script run in the editor only. In 2.0 you could check if you were in the editor with get_tree().is_editor_hint(), but this has been removed in 3.0 and I can’t see any replacement in the SceneTree class.
  2. Some way to get the current 2D snap settings. Doesn’t look like this is possible.
  3. Test if the node is currently selected in the editor. You can do this with self in EditorPlugin.new().get_editor_interface().get_selection(), but it seems like there should be a way to do this without creating an EditorPlugin.
  4. Handle input events. I tried set_process_input(true), but it doesn’t receive input events in the editor.

I can do #1, #3, and #4 if I use an EditorPlugin with a custom node. First I give my custom node script this function:

func plugin_input(xform, event):
	print(xform, event)

Then my EditorPlugin forwards any input events to the currently selected node if that node is the custom node. The only way I’ve found to test if the selected object in handles(object) is the custom node is to check if it has the custom node script.

tool
extends EditorPlugin

var current_selection
var selection_is_custom_type

func _enter_tree():
	add_custom_type("MyCustomType", "Node2D", preload("custom_script.gd"), preload("icon.png"))

func _exit_tree():
	remove_custom_type("MyCustomType")

func handles(object):
	current_selection = object
	selection_is_custom_type = object.get_script() == preload("custom_script.gd")

func forward_canvas_gui_input(xform, event):
	if selection_is_custom_type:
		current_selection.plugin_input(xform, event)

This is fine but I would sometimes like to be able to create simple tool scripts without making an EditorPlugin or a custom type. I think testing if a node is selected in the editor and then handling inputs is pretty essential for making tool scripts so maybe there’s a way to do this that I’m not seeing?

for #1, you can find it easily with Search Help.

volzhs | 2017-09-11 11:06

Ah, thats where it went. Thanks!

Smellyhobo101 | 2017-09-11 15:54

:bust_in_silhouette: Reply From: Smellyhobo101

Figured out #2 but it’s not pretty. The CanvasItemEditor holds all the current snap settings, and even has snap_point() and snap_angle() methods, but none of its methods or members are exposed to GDscript so I cant use it. So the only other way to get the snap settings is to read them straight from the editor controls which are children of the CanvasItemEditor. Have to recursively search all the editor nodes to even get the CanvasItemEditor to begin with. I figured out which of children I needed to get by investigating the canvas_item_editor_plugin.cpp. Here is the code:

tool
extends EditorPlugin

var snap_spinbox
var snap_popup

var snap_offset
var snap_step
var snap_rotation_step
var snap_rotation_offset

var snap_grid
var snap_show_grid
var snap_rotation
var snap_relative
var snap_pixel

func recursive_get_children(node):
	var children = node.get_children()
	if children.size() == 0:
		return []
	else:
		for child in children:
			children += recursive_get_children(child)
		return children
	
func find_snap_controls():
	var base_control = get_editor_interface().get_base_control()
	var children = recursive_get_children(base_control)

	var canvas_item_editor
	var snap_dialog
	for child in children:
		if child.get_class() == "CanvasItemEditor":
			canvas_item_editor = child
		if child.get_class() == "SnapDialog":
			snap_dialog = child
	
	var hbox = canvas_item_editor.get_child(0)
	for child in hbox.get_children():
		if child is MenuButton and child.get_text() == "Edit":
			snap_popup = child.get_popup()
	
	snap_spinbox = []
	for child in recursive_get_children(snap_dialog):
		if child.get_class() == "SpinBox":
			snap_spinbox.append(child)

func update_snap_settings():
	find_snap_controls()
	snap_grid = snap_popup.is_item_checked(0)
	snap_show_grid = snap_popup.is_item_checked(1)
	snap_rotation = snap_popup.is_item_checked(2)
	snap_relative = snap_popup.is_item_checked(3)
	snap_pixel = snap_popup.is_item_checked(6)
	
	snap_offset = Vector2(snap_spinbox[0].get_value(), snap_spinbox[1].get_value())
	snap_step = Vector2(snap_spinbox[2].get_value(), snap_spinbox[3].get_value())
	snap_rotation_step = snap_spinbox[4].get_value()
	snap_rotation_offset = snap_spinbox[5].get_value()

I created an issue for this and groud is apparently working on better snap functions that will be exposed to GDscript.