Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a way for EditorPlugins to get 2D Editor Grid Snap #10529

Open
IntangibleMatter opened this issue Aug 22, 2024 · 7 comments · May be fixed by godotengine/godot#101114
Open

Implement a way for EditorPlugins to get 2D Editor Grid Snap #10529

IntangibleMatter opened this issue Aug 22, 2024 · 7 comments · May be fixed by godotengine/godot#101114

Comments

@IntangibleMatter
Copy link

Describe the project you are working on

A plugin with a node that has custom handles for scaling/moving in the editor.

Describe the problem or limitation you are having in your project

There is no way to access grid snap to force the item to snap to the grid by default

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Implement some sort of get_grid_snap() function on EditorPlugin, EditorInterface, or the otherwise most relevant class.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

You would call a function to get the snapping intervals, as well as whether snap is enabled/disabled.

If this enhancement will not be used often, can it be worked around with a few lines of script?

Nope, you need to implement an entire grid system yourself, which is generally much more confusing to people using the plugin.

Is there a reason why this should be core and not an add-on in the asset library?

Core editor functionality.

@KoBeWi
Copy link
Member

KoBeWi commented Aug 22, 2024

You can hack it around by finding the snap dialog in scene tree and reading its values.

@IntangibleMatter
Copy link
Author

You can hack it around by finding the snap dialog in scene tree and reading its values.

This is a good temporary solution and I thank you for it, but it's still a needless hack considering how common this use case is for 2D plugins.

@IntangibleMatter
Copy link
Author

IntangibleMatter commented Aug 23, 2024

For anyone who comes across this in future and wants to use this, here's a version of the code working in Godot 4.3.0

var snap_enabled: bool = false
var snap_dimensions: Vector2 = Vector2.ZERO
var snap_offset: Vector2 = Vector2.ZERO

func _ready() -> void:
	connect_to_snap()


func connect_to_snap() -> void:
	if not Engine.is_editor_hint():
		return
	
	var snap: Node = EditorInterface.get_editor_main_screen()\
		.get_child(0).get_child(2).get_child(0).get_child(0)
	
	# this is a terrible line of code but I don't have a better way to do it
	var snaptoggle: Button = EditorInterface.get_editor_main_screen().\
		get_child(0).get_child(0).get_child(0).get_child(0).get_child(12)
	
	snap_enabled = snaptoggle.button_pressed
	snaptoggle.toggled.connect(func(tog: bool) -> void: snap_enabled = tog)
	
	# grid offset x
	snap.get_child(1).value_changed.connect(func(val: float) -> void: snap_offset.x = val)
	# grid offset y
	snap.get_child(2).value_changed.connect(func(val: float) -> void: snap_offset.y = val)
	snap_offset = Vector2(snap.get_child(1).value, snap.get_child(2).value)
	
	# grid snap x
	snap.get_child(7).value_changed.connect(func(val: float) -> void: snap_dimensions.y = val)
	# grid snap y
	snap.get_child(8).value_changed.connect(func(val: float) -> void: snap_dimensions.y = val)
	snap_dimensions = Vector2(snap.get_child(7).value, snap.get_child(8).value)

This code will break if any of the relevant nodes are offset in the tree, but this is the only way I could find to do it without being dependent on using .filter() and searching for exact strings, which isn't friendly to using other languages in the editor. Again, terrible. This is why there should be an easier way to access these variables. Hope this helps anyone in the future!

@KoBeWi
Copy link
Member

KoBeWi commented Aug 23, 2024

Finding base controls can be more reliable:

func nice_find_child(parent: Node, child: String) -> Node:
	return parent.find_child("*%s*" % child, true, false)

func connect_to_snap() -> void:
	var snap := nice_find_child(EditorInterface.get_base_control(), "SnapDialog") # SnapDialog
	snap = nice_find_child(snap_grid, "GridContainer") # SpinBox container.
	
	var snap_toggle := nice_find_child(EditorInterface.get_base_control(), "CanvasItemEditor") # 2D editor.
	snap_toggle = nice_find_child(snap_toggle, "HBoxContainer") # Toolbar.
	snap_toggle = snap_toggle.get_child(12) # Snap button.

You can also avoid get_child() by implementing a method that returns nth child of given type.

While it's still a hack, it's a bit more resilient to hierarchy changes.

@IntangibleMatter
Copy link
Author

I actually started out by using find_child(), but it wouldn't find the nodes that I wanted it to do so (likely due to weird stuff with internal children or something), so I used this method instead, though I'll take another look.

Again, while it's good to have a method to do so, something this important for 2D editor plugins shouldn't be so hacky.

@bitwes
Copy link

bitwes commented Nov 26, 2024

I found my way back here again as I'm trying to also make a set of handles for the editor

I made various helpers inspired by code found here. The methods all aimed to make it more obvious if a Editor's tree changed. You can find them all here:
https://gist.github.com/bitwes/ff5e3f9b47bb7f709df7a2b6c2605613

  • safe_get_descendant allows you specify an array of names and/or indexes to walk down the tree safely with one statement instead of chaining statements.
  • safe_get_descendant, safe_get_child. find_child_with_name_containing (nice_find_child with more stuff in it) all accept an optional expected_type so you can make sure you are at least getting the type of Node you expect.

Using these methods (loaded into the helpers variable below) I made the following which gets all the grid snap settings from the editor. It updates itself when the "ok" button is pressed on the "Configure Snap" dialog. It also has a found_all_editor_controls property that could be used to verify that all the nodes in the editor were found.

var helpers = load(<wherever you put the code I linked above>).new()
var found_all_editor_controls = false
var snap_enabled: bool = false
var snap_step : Vector2 = Vector2.ZERO
var snap_offset: Vector2 = Vector2.ZERO

var editor_controls = {
	dialog = null,
	grid_offset_x = null,
	grid_offset_y = null,
	grid_snap_x = null,
	grid_snap_y = null,
	snap_toggle = null
}


func _init():
	if(Engine.is_editor_hint()):
		setup_controls()
		update_values_from_editor()


func setup_controls():
	var main_screen = EditorInterface.get_editor_main_screen()
	editor_controls.snap_toggle = helpers.safe_get_descendant(main_screen, ["HBoxContainer", 12], Button)

	var snap_dia_grid_container = helpers.safe_get_descendant(main_screen, ["SnapDialog", "GridContainer"], GridContainer)
	editor_controls.grid_offset_x = helpers.safe_get_child(snap_dia_grid_container, 1, SpinBox)
	editor_controls.grid_offset_y = helpers.safe_get_child(snap_dia_grid_container, 2, SpinBox)
	editor_controls.grid_snap_x = helpers.safe_get_child(snap_dia_grid_container, 4, SpinBox)
	editor_controls.grid_snap_y = helpers.safe_get_child(snap_dia_grid_container, 5, SpinBox)
	editor_controls.dialog = helpers.find_child_with_name_containing(main_screen, "SnapDialog", ConfirmationDialog)

	if(null in editor_controls.values()):
		push_error("One or more Editor Controls could not be found.")
		return
	else:
		found_all_editor_controls = true

	editor_controls.snap_toggle.toggled.connect(func(tog: bool) -> void: snap_enabled = tog)
	editor_controls.dialog.get_ok_button().pressed.connect(update_values_from_editor)


func update_values_from_editor():
	if(!found_all_editor_controls):
		return
	snap_enabled = editor_controls.snap_toggle.button_pressed
	snap_offset.x = editor_controls.grid_offset_x.value
	snap_offset.y = editor_controls.grid_offset_y.value
	snap_step.x = editor_controls.grid_snap_x.value
	snap_step.y = editor_controls.grid_snap_y.value

@erayzesen
Copy link

erayzesen commented Jan 3, 2025

I was looking for this feature back in Godot's 3rd version. Years have passed, I've moved on to version 4, and I find myself searching for a solution to this for the third time. I don’t understand why it’s made so inaccessible, at least on the EditorPlugin side. It’s a significant shortcoming.

Edit: I should also mention that the hacks shared above don’t always work reliably.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants