Editor/Drivers/AssetAndSelectionDriver.cs

Editor utility class that drives asset quick placement and selection syncing in the editor. QuickPlaceAsync searches cloud packages for a model match, spawns a placeholder GameObject at a given position/scale, selects it and returns info. SyncSelection finds a GameObject by name or id in the active scene, selects it and optionally indicates camera focus.

NetworkingFile Access
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sandbox;

namespace Editor.Mcp
{
	internal static class AssetAndSelectionDriver
	{
		public static async Task<ToolOutcome<object>> QuickPlaceAsync( string query, string source = "both", float posX = 0f, float posY = 0f, float posZ = 0f, float scale = 1.0f )
		{
			var scene = EditorRuntime.ResolveActiveScene();
			if ( scene == null ) return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "asset.no_scene", "No active scene loaded." );

			var normalized = (query ?? "").Trim();
			if ( string.IsNullOrWhiteSpace( normalized ) )
			{
				return ToolOutcome<object>.Failure( null, ToolStatus.Failed, "asset.query_required", "Query is required." );
			}

			// Search cloud packages for matching models/prefabs
			Package? matchedPackage = null;
			try
			{
				var packages = await Package.FindAsync( $"{normalized} type:model" );
				matchedPackage = packages?.Packages?.FirstOrDefault();
			}
			catch { /* fallback to local placeholder */ }

			var go = new GameObject( true, $"{normalized}_placeholder" );
			go.WorldPosition = new Vector3( posX, posY, posZ );
			go.WorldScale = new Vector3( scale, scale, scale );

			var renderer = go.Components.Create<ModelRenderer>();
			renderer.Model = Model.Cube;

			EditorScene.Selection.Clear();
			EditorScene.Selection.Add( go );

			return ToolOutcome<object>.Ready( new
			{
				Query = normalized,
				Source = source,
				PackageIdent = matchedPackage?.FullIdent,
				SpawnedGameObject = go.Name,
				Id = go.Id.ToString(),
				Position = go.WorldPosition
			} );
		}

		public static ToolOutcome<object> SyncSelection( string targetQuery, bool focusCamera = true )
		{
			var scene = EditorRuntime.ResolveActiveScene();
			if ( scene == null ) return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "select.no_scene", "No active scene loaded." );

			var go = FindObjectInScene( scene.Children, targetQuery );
			if ( go == null ) return ToolOutcome<object>.Failure( null, ToolStatus.Failed, "select.not_found", $"GameObject '{targetQuery}' not found." );

			EditorScene.Selection.Clear();
			EditorScene.Selection.Add( go );

			return ToolOutcome<object>.Ready( new
			{
				Selected = go.Name,
				Id = go.Id.ToString(),
				Position = go.WorldPosition,
				CameraFocused = focusCamera
			} );
		}

		private static GameObject? FindObjectInScene( IEnumerable<GameObject>? roots, string selector )
		{
			if ( roots == null ) return null;
			foreach ( var item in roots )
			{
				if ( item.Id.ToString().Equals( selector, StringComparison.OrdinalIgnoreCase ) || item.Name.Equals( selector, StringComparison.OrdinalIgnoreCase ) )
					return item;

				var nested = FindObjectInScene( item.Children, selector );
				if ( nested != null ) return nested;
			}
			return null;
		}
	}
}