What is the correct way of performing collision checks in a tool script?

Godot Version

4.8

Question

It seems that if I add, say, a StaticObject3D to the tree, then call direct_space_state.intersect_ray in the same tool script, the collider doesn’t always get taken into account. This makes perfect sense to me as the physics server is its own thing with its own lifecycle.

Is there a way to wait until physics synchronization has occurred? await get_tree().physics_frame is often stated to be the correct way to do this, but, in my testing, it only works most of the time. I still sometimes get collisions just not happening until I run the tool script again.

But I suspect that even this would be a band-aid and there’s a better way of performing reachability checks/etc. from within a tool script rather than using the direct_space_state API which seems like it isn’t intended for editor use. Is there something else I should be using?

What do you use raycasts for in a tool script?
Post the code.

I’ll include the code but I think it’s my approach which is wrong, not my implementation.

var shape_query: PhysicsShapeQueryParameters3D = PhysicsShapeQueryParameters3D.new()
shape_query.collide_with_areas = true
shape_query.collide_with_bodies = false
shape_query.collision_mask = collision_mask

var world: World3D = get_viewport().find_world_3d()
assert(world != null, "Failed to find World3D.")
var direct_space_state: PhysicsDirectSpaceState3D = world.direct_space_state

for node: Node in editable_region.get_children():
	if node is CollisionShape3D:
		shape_query.shape = node.shape
		shape_query.transform = node.global_transform

		var collisions: Array[Dictionary] = direct_space_state.intersect_shape(shape_query, maximum_count)
		assert(len(collisions) < maximum_count, "Maximum count exceeded.")

		print("REGION FOUND: ", len(collisions), " ", collision_mask, " ", shape_query.shape, " ", shape_query.transform)

Some runs you’ll get nothing:

REGION FOUND: 0 32768 <BoxShape3D#-9223354138115329563> [X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (-15.09717, 0.200439, -13.97986)]
REGION FOUND: 0 32768 <BoxShape3D#-9223354138098561729> [X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (-1.153503, 4.65802, 10.23761)]
REGION FOUND: 0 32768 <BoxShape3D#-9223354138081777779> [X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (8.301239, 0.200439, -13.97986)]

But run the script again without changing anything and it works:

REGION FOUND: 180 32768 <BoxShape3D#-9223354138115329563> [X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (-15.09717, 0.200439, -13.97986)]
REGION FOUND: 592 32768 <BoxShape3D#-9223354138098561729> [X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (-1.153503, 4.65802, 10.23761)]
REGION FOUND: 196 32768 <BoxShape3D#-9223354138081777779> [X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (8.301239, 0.200439, -13.97986)]

Who calls this code and when?

This is directly in the callable attached to a @export_tool_button. Pressing it twice in a row without doing anything in between causes different results. Which is why I don’t think this is an implementation issue but an approach issue. I don’t think I’m supposed to be performing physics queries against the scene from a tool script as it doesn’t appear to be reliable (which makes sense, physics are mostly suspended in the editor). But I can’t find any alternatives.

Why precisely do you need the physics queries for? Describe what you’re trying to achieve.

I’m primarily using them for static reachability checks, i.e. which nodes can be reached from which nodes (a combination of shape tests to find shortlists within range and ray tests to ensure that line of sight exists).

The sample posted above is for something much simpler, but exhibits the same symptoms; I’m marking regions of the map using areas and checking which nodes fall within them using shape tests.

In all cases I’m seeing that they usually work but not always, sometimes nothing comes back at all. Given that this is reliant upon the physics engine which is suspended during edit time I’m assuming that these APIs are not intended for use within tool scripts which is why I’m seeking alternatives.

Try PhysicsServer3D.set_active(true), then await one physics frame, do the query and then disable it.

Unfortunately that doesn’t seem to make any difference. Also tried awaiting two physics frames. Good idea though, hadn’t found that method in my travels.

Can you make a minimal example that demonstrates that set_active() doesn’t update the state?

Well, I figured it out.

It was an oversight elsewhere in the codebase causing a layer change to be accidentally deferred. So the colliders DID exist, and WERE on the right layer by the time the editor renders, but NOT at the time the collision check happened.

Thank you for some really great suggestions and being patient with me.

It almost always is :wink: