Issues displaying a 3d scene in an editor panel plugin

Godot Version

4.2

Question

Trying my hand at a bit more complex plugin, but because of my lack of Godot plugin experience I’ve ran into several issues and a lot of tutorials online are outdated, which got me more confused. I think what i’m trying to do can be done, but not 100% sure. Any input/suggestions would be greatly appreciated!

I’m trying to create an editor panel control that contains a SubViewport to run a separate 3D scene that can be interacted with. For my first experiment, this scene is just an orbit camera, with a couple cubes where one follows the cursor. When i run this scene alone, everything works as expected:

2024-01-20 12-00-36

However, my issues arise when i use this in an editor panel. There are a few different issues:

  1. It loads this scene into the main 3d viewport for everything, even a brand new scene.
  2. The orbit camera doesn’t work anymore when i hold down middle mouse. (Mouse input not passing properly?)
  3. The red ‘cursor cube’ does track the mouse still, but it does it across all of the Godot UI and not just when the cursor is over the viewport on the right

PreviewScript.cs - Attached to the preview node above

#if TOOLS
using Godot;  
using System;  
  
[Tool]  
public partial class PreviewScene : Node3D  
{  
    private MeshInstance3D _redCube;  
    private Camera3D _camera3D;  
    
	public override void _Ready()  
    {       
       _camera3D = GetNode<Camera3D>("Node3D/Camera3D");  
       CreateCursor();  
    }  
      
    public override void _Process(double delta)  { }  
      
    public override void _PhysicsProcess(double delta)  
    {       
		if (Input.IsMouseButtonPressed(MouseButton.Middle)) { return; }  
		
		var from = _camera3D.ProjectRayOrigin(GetViewport().GetMousePosition());  
		var to = from + _camera3D.ProjectRayNormal(GetViewport().GetMousePosition()) * 1000;  
		
		var intersect = GetRayPlaneIntersection(from, to, new Vector3(0, 0, 0), new Vector3(0, 1, 0));  
		var rounded = new Vector3(Mathf.Round(intersect.X), Mathf.Round(intersect.Y), Mathf.Round(intersect.Z));  
		
		_redCube.Position = rounded;  
	}  
      
    public void CreateCursor()  
    {       
	    // Creates and places the red cube
    }    
    private Vector3 GetRayPlaneIntersection(Vector3 rayOrigin, Vector3 rayEnd, Vector3 planePoint, Vector3 planeNormal)  
    {       
		// Calculates the intersection  
    }
}
#endif

OrbitCamera.cs - Attached to camera

using Godot;
using Godot.Collections;

public partial class OrbitCamera : Camera3D
{
	[Export] public float ScrollSpeed = 10;
	[Export] public float RotateSpeed = 1;
	[Export] public NodePath AnchorNodePath;
	[Export] public float MouseZoomSpeed = 10;
	private Vector2 _moveSpeed;
	private Vector3 _rotation;
	private float _distance = 10;
	private Node3D _anchorNode;
	
	public override void _Ready()
	{
		_anchorNode = GetNode<Node3D>(AnchorNodePath);
		_rotation = _anchorNode.Transform.Basis.GetRotationQuaternion().GetEuler();
		_distance = DefaultDistance;
	}

	public override void _Process(double delta)
	{
		ProcessTransformation((float)delta);
	}
	  
	private void ProcessTransformation(float delta)
	{
		_rotation.X += -_moveSpeed.Y * delta * RotateSpeed;
		_rotation.Y += -_moveSpeed.X * delta * RotateSpeed;
	  
		if (_rotation.X < -Mathf.Pi) { _rotation.X = -Mathf.Pi / 2;	}
	  
		if (_rotation.X > Mathf.Pi / 2)	{ _rotation.X = Mathf.Pi / 2; }

		_moveSpeed = new Vector2();
	  	_distance += _scrollSpeed * delta;
		
		if (_distance < 0) { _distance = 0;	}
	  
		_scrollSpeed = 0;
	  
		SetIdentity();
		TranslateObjectLocal(new Vector3(0, 0, _distance));
		_anchorNode.SetIdentity();
		var t = _anchorNode.Transform;
		t.Basis = new Basis(Quaternion.FromEuler(_rotation));
		_anchorNode.Transform = t;
	}
	  
	public override void _Input(InputEvent @event)
	{
		if (@event is InputEventMouseMotion motion) { ProcessMouseRotationEvent(motion); }
	}
	  
	private void ProcessMouseRotationEvent(InputEventMouseMotion @event)
	{
		if (Input.IsActionPressed("orbit_camera"))
		{
			_moveSpeed = @event.Relative;
		}
	}
}

Plugin.cs

#if TOOLS  
using Godot;  
using System;  
  
[Tool]  
public partial class TestPlugin : EditorPlugin  
{  
    private Control ruleEditorControl;  
    public override void _EnterTree()  
    {       
	    ruleEditorControl = GD.Load<PackedScene>("res://addons/testplugin/scenes/RuleEditor.tscn").Instantiate() as Control;      
	    AddControlToDock(DockSlot.RightUr, ruleEditorControl);  
	    _MakeVisible(false);  
	    ruleEditorControl.Hide();  
    }  
    
    public override void _Process(double delta)  {    }  

	// Eventually only display the editor when a certain node is selected  
    private void OnEditorSelectionChanged()  { }  
  
    public override void _Input(InputEvent @event)  
    {       
	    ruleEditorControl.GetNode<SubViewport>("HBoxContainer/SubViewportContainer/SubViewport")._Input(@event);  
    }  
    
    public override void _ExitTree()  
    {       
	    RemoveControlFromDocks(ruleEditorControl);  
	    EditorInterface.Singleton.GetEditorMainScreen().RemoveChild(ruleEditorControl);  
	    ruleEditorControl?.QueueFree();  
    }  
    
    public override void _MakeVisible(bool visible)  
    {       
	    if (ruleEditorControl != null) {  
		    ruleEditorControl.Visible = visible;  
	    }    
	}  
	
    public override string _GetPluginName() { return "Test Plugin"; }  
    
}  
#endif

Since im a new member, i could only embed one image in previous post. Here is a combined shot of the scenes:

Top image shows the editor scene showing up in the main 3d viewport for a new scene

Bottom left is UI Scene with Subviewport to the Cube scene

Bottom right is the scene with the cubes