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

Expose editor viewports in EditorInterface #68696

Merged
merged 1 commit into from
Oct 2, 2023

Conversation

TokisanGames
Copy link
Contributor

@TokisanGames TokisanGames commented Nov 15, 2022

This PR grants gdscript access to the editor viewports, which grant access to the cameras, as discussed in godotengine/godot-proposals#1302.

For 3D Viewports

EditorInterface.get_editor_viewport_3d(p_index) -> SubViewport provides access to the 3d editor viewports.

The cameras can already be accessed with Viewport.get_camera_3d() (no change).

For 2D Viewports

EditorInterface.get_editor_viewport_2d() -> SubViewport provides access to the 2D viewport.

There is no 2D editor camera per @KoBeWi, as view transforms are done directly. The viewport transform can be accessed as shown in the example below.

Example Usage

This script shows accessing the 2D viewport and all 4 3D cameras. The positions are printed to the console as the 2D or any of the 4 3D viewport cameras are moved around.

@tool
extends Node

var editor_cameras: Array[Camera3D]
var editor_2dvp: SubViewport


func _ready():
	if Engine.is_editor_hint():
		editor_2dvp = EditorInterface.get_editor_viewport_2d()

		for i in 4:
			var vp = EditorInterface.get_editor_viewport_3d(i)
			var cam = vp.get_camera_3d()
			editor_cameras.push_back(cam)
		
		var timer := Timer.new()
		add_child(timer)
		timer.start(1)
		timer.timeout.connect(Callable(self, "_on_timeout"))


func _on_timeout():
	print("----------")
	print("2D Viewport: ", editor_2dvp.global_canvas_transform)
	
	for i in editor_cameras.size():
		print("3D Editor camera%d pos: %.2v" % [ i, editor_cameras[i].global_position])

Output

----------
2D Viewport: [X: (0.561231, 0), Y: (0, 0.561231), O: (122.801, 209.8038)]
3D Editor camera0 pos: (-0.26, 2.74, 1.12)
3D Editor camera1 pos: (3.51, 0.86, -1.22)
3D Editor camera2 pos: (-4.29, 1.56, -1.15)
3D Editor camera3 pos: (-1.66, 1.56, -3.97)

Update: This PR has been updated post #75694

@Chaosus
Copy link
Member

Chaosus commented Nov 15, 2022

Note - you can actually make the PR draft by pressing Convert to Draft button:

image

@TokisanGames TokisanGames marked this pull request as draft November 15, 2022 19:49
@TokisanGames TokisanGames force-pushed the expose_editor_viewports branch from 95a7b55 to b373a78 Compare November 15, 2022 20:44
@Calinou Calinou added this to the 4.0 milestone Nov 15, 2022
@TokisanGames TokisanGames marked this pull request as ready for review November 15, 2022 22:29
@TokisanGames
Copy link
Contributor Author

The C# mono build fails. What needs to change to allow this to call a static get_editor_interface()?

var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();

Error: /home/runner/work/godot/godot/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs(16,34): error CS0176: Member 'EditorPlugin.GetEditorInterface()' cannot be accessed with an instance reference; qualify it with a type name instead [/home/runner/work/godot/godot/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj]

@TokisanGames TokisanGames changed the title DRAFT: Expose editor viewports. Make get_editor_interface functions static. Expose editor viewports. Make get_editor_interface functions static. Nov 15, 2022
@raulsntos
Copy link
Member

This should fix the C#:

diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
index 9df90ac608..8acd701647 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -76,7 +76,7 @@ namespace GodotTools.Ides
 
         public async Task<EditorPick?> LaunchIdeAsync(int millisecondsTimeout = 10000)
         {
-            var editorId = (ExternalEditorId)(int)GodotSharpEditor.Instance.GetEditorInterface()
+            var editorId = (ExternalEditorId)(int)EditorPlugin.GetEditorInterface()
                 .GetEditorSettings().GetSetting("mono/editor/external_editor");
             string editorIdentity = GetExternalEditorIdentity(editorId);
 
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
index 60602a5847..fd7dcee221 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
@@ -13,7 +13,7 @@ namespace GodotTools.Ides.Rider
 
         private static string GetRiderPathFromSettings()
         {
-            var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+            var editorSettings = EditorPlugin.GetEditorInterface().GetEditorSettings();
             if (editorSettings.HasSetting(EditorPathSettingName))
                 return (string)editorSettings.GetSetting(EditorPathSettingName);
             return null;
@@ -21,7 +21,7 @@ namespace GodotTools.Ides.Rider
 
         public static void Initialize()
         {
-            var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+            var editorSettings = EditorPlugin.GetEditorInterface().GetEditorSettings();
             var editor = (ExternalEditorId)(int)editorSettings.GetSetting("mono/editor/external_editor");
             if (editor == ExternalEditorId.Rider)
             {
@@ -81,7 +81,7 @@ namespace GodotTools.Ides.Rider
                 return riderPath;
             }
 
-            var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+            var editorSettings = EditorPlugin.GetEditorInterface().GetEditorSettings();
             var paths = RiderPathLocator.GetAllRiderPaths();
 
             if (!paths.Any())

@TokisanGames
Copy link
Contributor Author

@raulsntos Excellent, thank you for the patch. I have applied it and built successfully.

@YuriSizov YuriSizov modified the milestones: 4.0, 4.1 Feb 10, 2023
@YuriSizov YuriSizov self-requested a review April 5, 2023 12:50
@YuriSizov YuriSizov modified the milestones: 4.1, 4.2 Jun 14, 2023
@YuriSizov
Copy link
Contributor

With #75694 merged, this needs to be updated to remove static changes. It also needs a rebase. Provided this is still desired, of course.

@TokisanGames TokisanGames force-pushed the expose_editor_viewports branch from b19ab44 to 4c1429a Compare August 13, 2023 18:59
@TokisanGames
Copy link
Contributor Author

It is desired. This has been rebased and static functions removed.

editor/editor_interface.cpp Outdated Show resolved Hide resolved
}

SubViewport *EditorInterface::get_editor_viewport_3d(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, static_cast<int>(Node3DEditor::VIEWPORTS_COUNT), nullptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ERR_FAIL_INDEX_V(p_idx, static_cast<int>(Node3DEditor::VIEWPORTS_COUNT), nullptr);
ERR_FAIL_INDEX_V(p_idx, int(Node3DEditor::VIEWPORTS_COUNT), nullptr);

Assuming the casting is required, this should work too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this line directly from Node3DEditorViewport::get_editor_viewport(). VIEWPORTS_COUNT is an unsigned int.

Isn't static_cast<int> preferred for C++ rather than C cast style?
https://stackoverflow.com/questions/1609163/what-is-the-difference-between-static-cast-and-c-style-casting

If you confirm C style cast is desired, I'll make the change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen C cast used more often.

Copy link
Member

@akien-mga akien-mga Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's nitpicky, but indeed we prefer C style casts throughout the codebase, unless there's a real ambiguity that a dedicated C++ casting expression would resolve.

We'd actually style it like this usually, which we find the most readable.

Suggested change
ERR_FAIL_INDEX_V(p_idx, static_cast<int>(Node3DEditor::VIEWPORTS_COUNT), nullptr);
ERR_FAIL_INDEX_V(p_idx, (int)Node3DEditor::VIEWPORTS_COUNT, nullptr);

Edit: I see this was likely taken from:

	Node3DEditorViewport *get_editor_viewport(int p_idx) {
		ERR_FAIL_INDEX_V(p_idx, static_cast<int>(VIEWPORTS_COUNT), nullptr);
		return viewports[p_idx];
	}

so I guess there was a precedent for this cast :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I copied the line from the downstream function. I wasn't partial and was looking for firm direction. Now I know C style is preferred. Thanks for addressing the PR.

@wayhoww
Copy link

wayhoww commented Oct 1, 2023

Are there any existing problems with this PR except a (trivial) code conflict? If not:

Apologize for inconvenience.

@TokisanGames
Copy link
Contributor Author

TokisanGames commented Oct 1, 2023

@yslking Wait, why would you make a new PR?
This PR has already been reviewed and approved. It can be rebased at any time by @akien-mga or by me upon request. All it's waiting on are the core devs to decide about it and merge in.

Edit: @yslking it's all good. It's also good to prompt the core devs to make a decision on work that is already done, reviewed, and approved.

@wayhoww
Copy link

wayhoww commented Oct 1, 2023

@TokisanGames. Ok. I see that this PR has been inactive for a long time, and there are too many PRs still open in the Godot project, so I want to do something to get this PR merged into master as soon as possible.

Thanks for your explanation, I've closed the new PR. If you don't mind, as a newbie in the open source community, I would like to ask how long it usually takes the core team to review and merge such a PR?

@Zireael07
Copy link
Contributor

There is no "how long usually" in Godot repository. Some PRs get merged very fast, within weeks. Some languish for years.

@AThousandShips
Copy link
Member

So some procedure and advice, but let's not get deeper into this as it is off topic to this:

  1. Communicate, do not assume that a PR is abandoned unless it has been communicated by the author, unless the author's account has been closed
  2. Minor details such as rebasing, or fixing minor review comments will be done by maintainers, not something that needs salvaging, only when a PR has changes that needs major work (like rewriting something to fit a changed API, or a new direction) is it relevant to salvage it
  3. Just over a month is not a long time for a PR to be inactive when no major work is required, just a rebase is not a major issue that indicates anything, some contributors like to keep their PRs up to date always, and some wait until they need to
  4. Any contributions are welcome, but make sure your contribution is actually needed, or that what you are doing is welcome and appropriate, and as always, communicate

@TokisanGames TokisanGames force-pushed the expose_editor_viewports branch from fefffd8 to 110130b Compare October 1, 2023 15:05
@TokisanGames
Copy link
Contributor Author

Rebased and happy to do so again upon request from core devs. @YuriSizov @akien-mga Is there any resistance to merging this in 4.2?

Copy link
Member

@akien-mga akien-mga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@akien-mga akien-mga changed the title Expose editor viewports Expose editor viewports in EditorInterface Oct 2, 2023
@akien-mga akien-mga merged commit 365ee41 into godotengine:master Oct 2, 2023
15 checks passed
@akien-mga
Copy link
Member

Thanks!

@saminton
Copy link

I may be missing something but can we not get the current viewport / camera ?

Say I'm trying to create a planar reflection that I want to work in the editor, for it to work in all 4 views I need to retrieve the appropriate camera for each view. At the moment it doesn't appear that this is possible.

@AThousandShips
Copy link
Member

AThousandShips commented Jan 20, 2024

It doesn't work to do EditorInterface.get_editor_viewport_3d(i).get_camera_3d()? How does it not work?

@TokisanGames
Copy link
Contributor Author

Not only is it possible, the code at the top of this PR shows you exactly how to get all 4 editor viewports and their cameras. Use Godot 4.2. Make a new scene with a node. Put that script on it, and you will see the output shown.

@saminton
Copy link

@AThousandShips @TokisanGames Yes, you can get all viewports and all cameras. But I only want to retrieve the current camera relative to the view, so view 1 gets the camera 1, view 2 gets the camera 2 etc.

EditorInterface.get_editor_viewport_3d(i).get_camera_3d()
For that I need to know the index of the current view, but how do you get the value i ?

image

@AThousandShips
Copy link
Member

That's not implemented, please open a proposal for it as there's no current system for it as far as I can see easily accessible

@saminton
Copy link

Ok thanks, just wanted to make sure I wasn't missing something before opening a proposal.

@TokisanGames
Copy link
Contributor Author

You know the value of i. viewport 0 : i = 0, viewport 3 : i = 3. You know exactly which camera is in which viewport. Also each camera is a child of each viewport, so it's not possible to have any ambiguity. They are attached to each other.

You also know the viewport and camera nodes, which means you can connect to signals and functions of those and surrounding nodes. They all report signals.

For instance you could have printed the parental tree of an editor camera:
@SubViewportContainer@9525/@SubViewport@9526/@Camera3D@9528

...and discovered that the viewport is in a SubViewportContainer which is a Control node, which sends out signals every time the mouse enters it, anytime there is keyboard input, or it's resized, or a bunch of other things.

I don't think you need another proposal. You just need to learn the API better, which is done through study of the docs and experimentation over time.

@saminton
Copy link

@TokisanGames I do not follow. It's not a problem of knowing which camera is attached to which viewport. It's about finding the camera that is responsible for rendering each of the 4 views when using the "split" view in the editor.

Screenshot 2024-01-21 at 18 05 15

Yes I—the user—know which value i should be but I can't figure how to find the value of i through a script attached to an object in the scene. Say I have the following script. This script is attached to the plane in my previous screenshot.

@tool
extends MeshInstance3D

func _ready():
	if Engine.is_editor_hint():
		var i = 1; // how do I find `i` ?
		var current_viewport = EditorInterface.get_editor_viewport_3d(i);
		var current_cam = current_viewport.get_child(0); // editor camera
	else:
		base_cam = window.get_camera_3d(); // in game camera

How is the script here supposed to know though which camera the scene is being viewed (which will then allow me to get the current viewport texture to use on the plane) ?

This is not a problem with the PR per say and am not criticising it in any way. I am learning Godot and thought getting the camera of the current view would be rather trivial. It's just turning out to be a lot more complex than I initially thought. I have spent the last two days looking through the API and examples but have hit this roadblock, and seeing as this PR is very close to what I am looking for that I may be able to find an answer here.

@TokisanGames
Copy link
Contributor Author

A node can run get_viewport().

However, I think you have bigger problems in your conception of this scenario than figuring out the viewport.

You don't have 4 scenes or Worlds. You have 1. Your one object will run that script once. It won't run it 4 times, one for each camera/vp. If the mesh printed the viewport number on its texture, it would show 0 (or 1) on all 4, and that would be working properly. After it runs once, it then stops.

Then if you intend to put the viewport texture on the object, you'll have another problem. You have 1 mesh instance, not 4. You have one material with one texture. If it ran properly the first time and grabbed the texture from the first viewport, it would show the first viewport texture in all of the other windows.

https://docs.godotengine.org/en/stable/tutorials/rendering/viewports.html#capture

The only way I can think of to show the texture differently in each viewport is to use a screen space shader and perhaps that could copy it for each vp, but I'm not positive that would work. Otherwise, if you want to have separate materials and textures per viewport, you need 4 separate Worlds and mesh instances and materials.

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

Successfully merging this pull request may close these issues.