How do I retrieve all data from my Gizmo's Node3D instance?

Godot Version

v4.2.1.stable.mono.official [b09f793f5]

Context

I’m currently attempting to create a gizmo plugin for one of my custom classes. The good news is that I can draw gizmos and position them using the node’s transform. That’s great.

However, since I’m building this gizmo for one of my custom classes, I would like to retrieve its specialized information such that I can configure the gizmo from that data.

One of the problems I’m running into is being unable to cast the Node3D to my custom class.

// This produces a null reference exception (even though this works elsewhere)
MyClass customClass = gizmo.GetNode3D() as MyClass;

Because of this, I instead tried to use the Get() method to retrieve the data that I need.
This works for all supported Variant types (int, string, Vector3 etc.) so I’m now able to retrieve most of the data. The problem with this approach is that I am unable to retrieve variables whose type is not a Variant (in my case, I have created a class called Octree with no base class).

My attempt at fixing this was based on the documentation that states (see C# Variant)

So I tried to make my Octree class derive from GodotObject. My hope was that I could then do

Octree oct = region.Get("octree").As<Octree>();

…since Octree was now “Variant-compatible”. This turned out to not be the case. I even tried to tweak the casting (based on this post), but this didn’t work either.

Octree oct = region.Get("octree").AsGodotObject() as Octree;

Question

  • Is it not possible to cast the Node3D associated with a 3D gizmo to a custom class?
    • If not, is there another way to retrieve the non-Variant data that I need from the instance?

Any help with this is greatly appreciated!

Code

OctreeGizmo.cs

Look at _HasGizmo() and _Redraw() and how casting the Node3D, in both contexts, is invalid.

using Godot;
using System;
using System.Collections.Generic;

[Tool]
public partial class OctreeGizmo : EditorNode3DGizmoPlugin
{
    private Mesh mesh;
    
    public OctreeGizmo()
    {   
        CreateMaterial("main", new Color(1f, 1f, 0f, 0.2f));
        
        mesh = new BoxMesh() { Size = Vector3.One * 2f };
    }

    public override string _GetGizmoName()
    {
        return "Octree Region 3D";
    }

    public override bool _HasGizmo(Node3D node3D)
    {
        bool hasGizmo = node3D is OctreeRegion3D;
        GD.Print($"Comparing {node3D.Name} node to type OctreeRegion3D | result: {hasGizmo}");
        GD.Print($"Node is of type {node3D.GetType()}");
        // The second print-statement prints the following:
        // Node is of type Node3D

        return hasGizmo;
    }

    public override void _Redraw(EditorNode3DGizmo gizmo)
    {
        GD.Print($"Drawing gizmo for node: {gizmo.GetNode3D().Name}");
        
        gizmo.Clear();
        
        // GD.Print($"Script type on gizmo's node3D: {gizmo.GetNode3D().GetScript().As<OctreeRegion3D>()}");
        
        var region = gizmo.GetNode3D();
        
        Octree oct = region.Get("octree").AsGodotObject() as Octree;
        GD.Print($"Octree data test | Octree: {oct.IsValid()}");
        
        Vector3[] lines = new Vector3[]
        {
            Vector3.Up,
            Vector3.Up * 3f + Vector3.Right
        };
        
        gizmo.AddLines(lines, GetMaterial("main", gizmo), false);
        gizmo.AddMesh(mesh, GetMaterial("main", gizmo), region.Transform);
    }
}
OctreeRegion3DPlugin.cs
#if TOOLS
using Godot;
using System;

[Tool]
public partial class OctreeRegion3DPlugin : EditorPlugin
{
	OctreeGizmo gizmo = new OctreeGizmo();
	
	public override void _EnterTree()
	{
		// Add the gizmo for the script
		AddNode3DGizmoPlugin(gizmo);
		
		// // Add the script as a custom type (functions similarly, maybe even identically, to using the [GlobalClass] attribute)
		// var script = GD.Load<Script>("res://Scripts/Navigation/OctreeRegion3D.cs");
		// AddCustomType("OctreeRegion3D", "Node3D", script, null);
	}

	public override void _ExitTree()
	{
		// Clean-up of the plugin goes here.
		RemoveNode3DGizmoPlugin(gizmo);
	}
}
#endif
OctreeRegion3D.cs
using Godot;
using System;

[GlobalClass]
public partial class OctreeRegion3D : Node3D
{
	
	[Export] private Vector3 regionOffset;
	[Export] private float rootSize = 100f;
	[Export] private int maxDepth = 8;
	
	private Octree octree;
	public Octree Octree { get => octree; }
	
	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{	
		octree = new Octree(this, regionOffset, rootSize, maxDepth);
	}

	// Called every frame. 'delta' is the elapsed time since the previous frame.
	public override void _Process(double delta)
	{
	}
}
Octree.cs
using Godot;
using System;

public partial class Octree : GodotObject
{
  // ========================================================================
  // There is some stuff in this class, but it's not relevant to the problem
  // ========================================================================
}

I think I arrived at the cause of my issue.

It looks like [Tool] scripts exist in their own assembly or something.
After looking at this post on GitHub, I fixed the issue by giving my custom class both attributes: [GlobalClass] and [Tool].

Before:

[GlobalClass]
public partial class OctreeRegion3D : Node3D { }

After:

[GlobalClass, Tool]
public partial class OctreeRegion3D : Node3D { }

It would be nice if this was mentioned in the documentation.
if it is, please drop a link to the spot.