[FEATURE] Camera Capture (#449)
* Updates on Camera Capture Feature * Enable Camera Capture through both play and editor mode Notes: Because the standard ScreenCapture.CaptureScreenshot does not work in editor mode, so we use ScreenCapture.CaptureScreenshotIntoRenderTexture to enable it during play mode. * The user can access the camera access through the tool menu or through direct LLM calling. Both tested on Windows with Claude Desktop. * Minor changes nitpicking changesmain
parent
8a17cde29e
commit
97b85749b5
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using MCPForUnity.Editor.Helpers; // For Response class
|
||||
using MCPForUnity.Runtime.Helpers; // For ScreenshotUtility
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
|
|
@ -23,6 +24,8 @@ namespace MCPForUnity.Editor.Tools
|
|||
public string name { get; set; } = string.Empty;
|
||||
public string path { get; set; } = string.Empty;
|
||||
public int? buildIndex { get; set; }
|
||||
public string fileName { get; set; } = string.Empty;
|
||||
public int? superSize { get; set; }
|
||||
}
|
||||
|
||||
private static SceneCommand ToSceneCommand(JObject p)
|
||||
|
|
@ -42,7 +45,9 @@ namespace MCPForUnity.Editor.Tools
|
|||
action = (p["action"]?.ToString() ?? string.Empty).Trim().ToLowerInvariant(),
|
||||
name = p["name"]?.ToString() ?? string.Empty,
|
||||
path = p["path"]?.ToString() ?? string.Empty,
|
||||
buildIndex = BI(p["buildIndex"] ?? p["build_index"])
|
||||
buildIndex = BI(p["buildIndex"] ?? p["build_index"]),
|
||||
fileName = (p["fileName"] ?? p["filename"])?.ToString() ?? string.Empty,
|
||||
superSize = BI(p["superSize"] ?? p["super_size"] ?? p["supersize"])
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -142,14 +147,26 @@ namespace MCPForUnity.Editor.Tools
|
|||
return ga;
|
||||
case "get_build_settings":
|
||||
return GetBuildSettingsScenes();
|
||||
case "screenshot":
|
||||
return CaptureScreenshot(cmd.fileName, cmd.superSize);
|
||||
// Add cases for modifying build settings, additive loading, unloading etc.
|
||||
default:
|
||||
return new ErrorResponse(
|
||||
$"Unknown action: '{action}'. Valid actions: create, load, save, get_hierarchy, get_active, get_build_settings."
|
||||
$"Unknown action: '{action}'. Valid actions: create, load, save, get_hierarchy, get_active, get_build_settings, screenshot."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures a screenshot to Assets/Screenshots and returns a response payload.
|
||||
/// Public so the tools UI can reuse the same logic without duplicating parameters.
|
||||
/// Available in both Edit Mode and Play Mode.
|
||||
/// </summary>
|
||||
public static object ExecuteScreenshot(string fileName = null, int? superSize = null)
|
||||
{
|
||||
return CaptureScreenshot(fileName, superSize);
|
||||
}
|
||||
|
||||
private static object CreateScene(string fullPath, string relativePath)
|
||||
{
|
||||
if (File.Exists(fullPath))
|
||||
|
|
@ -329,6 +346,55 @@ namespace MCPForUnity.Editor.Tools
|
|||
}
|
||||
}
|
||||
|
||||
private static object CaptureScreenshot(string fileName, int? superSize)
|
||||
{
|
||||
try
|
||||
{
|
||||
int resolvedSuperSize = (superSize.HasValue && superSize.Value > 0) ? superSize.Value : 1;
|
||||
ScreenshotCaptureResult result;
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
result = ScreenshotUtility.CaptureToAssetsFolder(fileName, resolvedSuperSize, ensureUniqueFileName: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Edit Mode path: render from the best-guess camera using RenderTexture.
|
||||
Camera cam = Camera.main;
|
||||
if (cam == null)
|
||||
{
|
||||
var cams = UnityEngine.Object.FindObjectsOfType<Camera>();
|
||||
cam = cams.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (cam == null)
|
||||
{
|
||||
return new ErrorResponse("No camera found to capture screenshot in Edit Mode.");
|
||||
}
|
||||
|
||||
result = ScreenshotUtility.CaptureFromCameraToAssetsFolder(cam, fileName, resolvedSuperSize, ensureUniqueFileName: true);
|
||||
}
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
string message = $"Screenshot captured to '{result.AssetsRelativePath}' (full: {result.FullPath}).";
|
||||
|
||||
return new SuccessResponse(
|
||||
message,
|
||||
new
|
||||
{
|
||||
path = result.AssetsRelativePath,
|
||||
fullPath = result.FullPath,
|
||||
superSize = result.SuperSize,
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"Error capturing screenshot: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static object GetActiveSceneInfo()
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using MCPForUnity.Editor.Constants;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Services;
|
||||
using MCPForUnity.Editor.Tools;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
|
|
@ -199,6 +200,11 @@ namespace MCPForUnity.Editor.Windows.Components.Tools
|
|||
row.Add(parametersLabel);
|
||||
}
|
||||
|
||||
if (IsManageSceneTool(tool))
|
||||
{
|
||||
row.Add(CreateManageSceneActions());
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
|
|
@ -258,6 +264,47 @@ namespace MCPForUnity.Editor.Windows.Components.Tools
|
|||
categoryContainer?.Add(label);
|
||||
}
|
||||
|
||||
private VisualElement CreateManageSceneActions()
|
||||
{
|
||||
var actions = new VisualElement();
|
||||
actions.AddToClassList("tool-item-actions");
|
||||
|
||||
var screenshotButton = new Button(OnManageSceneScreenshotClicked)
|
||||
{
|
||||
text = "Capture Screenshot"
|
||||
};
|
||||
screenshotButton.AddToClassList("tool-action-button");
|
||||
screenshotButton.style.marginTop = 4;
|
||||
screenshotButton.tooltip = "Capture a screenshot to Assets/Screenshots via manage_scene.";
|
||||
|
||||
actions.Add(screenshotButton);
|
||||
return actions;
|
||||
}
|
||||
|
||||
private void OnManageSceneScreenshotClicked()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = ManageScene.ExecuteScreenshot();
|
||||
if (response is SuccessResponse success && !string.IsNullOrWhiteSpace(success.Message))
|
||||
{
|
||||
McpLog.Info(success.Message);
|
||||
}
|
||||
else if (response is ErrorResponse error && !string.IsNullOrWhiteSpace(error.Error))
|
||||
{
|
||||
McpLog.Error(error.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
McpLog.Info("Screenshot capture requested.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
McpLog.Error($"Failed to capture screenshot: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static Label CreateTag(string text)
|
||||
{
|
||||
var tag = new Label(text);
|
||||
|
|
@ -265,6 +312,8 @@ namespace MCPForUnity.Editor.Windows.Components.Tools
|
|||
return tag;
|
||||
}
|
||||
|
||||
private static bool IsManageSceneTool(ToolMetadata tool) => string.Equals(tool?.Name, "manage_scene", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static bool IsBuiltIn(ToolMetadata tool) => tool?.IsBuiltIn ?? false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,363 +1,363 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Services;
|
||||
using MCPForUnity.Editor.Windows.Components.ClientConfig;
|
||||
using MCPForUnity.Editor.Windows.Components.Connection;
|
||||
using MCPForUnity.Editor.Windows.Components.Settings;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using MCPForUnity.Editor.Constants;
|
||||
using MCPForUnity.Editor.Windows.Components.Tools;
|
||||
|
||||
namespace MCPForUnity.Editor.Windows
|
||||
{
|
||||
public class MCPForUnityEditorWindow : EditorWindow
|
||||
{
|
||||
// Section controllers
|
||||
private McpSettingsSection settingsSection;
|
||||
private McpConnectionSection connectionSection;
|
||||
private McpClientConfigSection clientConfigSection;
|
||||
private McpToolsSection toolsSection;
|
||||
|
||||
private ToolbarToggle settingsTabToggle;
|
||||
private ToolbarToggle toolsTabToggle;
|
||||
private VisualElement settingsPanel;
|
||||
private VisualElement toolsPanel;
|
||||
|
||||
private static readonly HashSet<MCPForUnityEditorWindow> OpenWindows = new();
|
||||
private bool guiCreated = false;
|
||||
private double lastRefreshTime = 0;
|
||||
private const double RefreshDebounceSeconds = 0.5;
|
||||
|
||||
private enum ActivePanel
|
||||
{
|
||||
Settings,
|
||||
Tools
|
||||
}
|
||||
|
||||
internal static void CloseAllWindows()
|
||||
{
|
||||
var windows = OpenWindows.Where(window => window != null).ToArray();
|
||||
foreach (var window in windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<MCPForUnityEditorWindow>("MCP For Unity");
|
||||
window.minSize = new Vector2(500, 600);
|
||||
}
|
||||
|
||||
// Helper to check and manage open windows from other classes
|
||||
public static bool HasAnyOpenWindow()
|
||||
{
|
||||
return OpenWindows.Count > 0;
|
||||
}
|
||||
|
||||
public static void CloseAllOpenWindows()
|
||||
{
|
||||
if (OpenWindows.Count == 0)
|
||||
return;
|
||||
|
||||
// Copy to array to avoid modifying the collection while iterating
|
||||
var arr = new MCPForUnityEditorWindow[OpenWindows.Count];
|
||||
OpenWindows.CopyTo(arr);
|
||||
foreach (var window in arr)
|
||||
{
|
||||
try
|
||||
{
|
||||
window?.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
McpLog.Warn($"Error closing MCP window: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateGUI()
|
||||
{
|
||||
// Guard against repeated CreateGUI calls (e.g., domain reloads)
|
||||
if (guiCreated)
|
||||
return;
|
||||
|
||||
string basePath = AssetPathUtility.GetMcpPackageRootPath();
|
||||
|
||||
// Load main window UXML
|
||||
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
||||
$"{basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml"
|
||||
);
|
||||
|
||||
if (visualTree == null)
|
||||
{
|
||||
McpLog.Error(
|
||||
$"Failed to load UXML at: {basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
visualTree.CloneTree(rootVisualElement);
|
||||
|
||||
// Load main window USS
|
||||
var mainStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(
|
||||
$"{basePath}/Editor/Windows/MCPForUnityEditorWindow.uss"
|
||||
);
|
||||
if (mainStyleSheet != null)
|
||||
{
|
||||
rootVisualElement.styleSheets.Add(mainStyleSheet);
|
||||
}
|
||||
|
||||
// Load common USS
|
||||
var commonStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(
|
||||
$"{basePath}/Editor/Windows/Components/Common.uss"
|
||||
);
|
||||
if (commonStyleSheet != null)
|
||||
{
|
||||
rootVisualElement.styleSheets.Add(commonStyleSheet);
|
||||
}
|
||||
|
||||
settingsPanel = rootVisualElement.Q<VisualElement>("settings-panel");
|
||||
toolsPanel = rootVisualElement.Q<VisualElement>("tools-panel");
|
||||
var settingsContainer = rootVisualElement.Q<VisualElement>("settings-container");
|
||||
var toolsContainer = rootVisualElement.Q<VisualElement>("tools-container");
|
||||
|
||||
if (settingsPanel == null || toolsPanel == null)
|
||||
{
|
||||
McpLog.Error("Failed to find tab panels in UXML");
|
||||
return;
|
||||
}
|
||||
|
||||
if (settingsContainer == null)
|
||||
{
|
||||
McpLog.Error("Failed to find settings-container in UXML");
|
||||
return;
|
||||
}
|
||||
|
||||
if (toolsContainer == null)
|
||||
{
|
||||
McpLog.Error("Failed to find tools-container in UXML");
|
||||
return;
|
||||
}
|
||||
|
||||
SetupTabs();
|
||||
|
||||
// Load and initialize Settings section
|
||||
var settingsTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
||||
$"{basePath}/Editor/Windows/Components/Settings/McpSettingsSection.uxml"
|
||||
);
|
||||
if (settingsTree != null)
|
||||
{
|
||||
var settingsRoot = settingsTree.Instantiate();
|
||||
settingsContainer.Add(settingsRoot);
|
||||
settingsSection = new McpSettingsSection(settingsRoot);
|
||||
settingsSection.OnGitUrlChanged += () =>
|
||||
clientConfigSection?.UpdateManualConfiguration();
|
||||
settingsSection.OnHttpServerCommandUpdateRequested += () =>
|
||||
connectionSection?.UpdateHttpServerCommandDisplay();
|
||||
}
|
||||
|
||||
// Load and initialize Connection section
|
||||
var connectionTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
||||
$"{basePath}/Editor/Windows/Components/Connection/McpConnectionSection.uxml"
|
||||
);
|
||||
if (connectionTree != null)
|
||||
{
|
||||
var connectionRoot = connectionTree.Instantiate();
|
||||
settingsContainer.Add(connectionRoot);
|
||||
connectionSection = new McpConnectionSection(connectionRoot);
|
||||
connectionSection.OnManualConfigUpdateRequested += () =>
|
||||
clientConfigSection?.UpdateManualConfiguration();
|
||||
}
|
||||
|
||||
// Load and initialize Client Configuration section
|
||||
var clientConfigTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
||||
$"{basePath}/Editor/Windows/Components/ClientConfig/McpClientConfigSection.uxml"
|
||||
);
|
||||
if (clientConfigTree != null)
|
||||
{
|
||||
var clientConfigRoot = clientConfigTree.Instantiate();
|
||||
settingsContainer.Add(clientConfigRoot);
|
||||
clientConfigSection = new McpClientConfigSection(clientConfigRoot);
|
||||
}
|
||||
|
||||
// Load and initialize Tools section
|
||||
var toolsTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
||||
$"{basePath}/Editor/Windows/Components/Tools/McpToolsSection.uxml"
|
||||
);
|
||||
if (toolsTree != null)
|
||||
{
|
||||
var toolsRoot = toolsTree.Instantiate();
|
||||
toolsContainer.Add(toolsRoot);
|
||||
toolsSection = new McpToolsSection(toolsRoot);
|
||||
toolsSection.Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
McpLog.Warn("Failed to load tools section UXML. Tool configuration will be unavailable.");
|
||||
}
|
||||
guiCreated = true;
|
||||
|
||||
// Initial updates
|
||||
RefreshAllData();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
OpenWindows.Add(this);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
OpenWindows.Remove(this);
|
||||
guiCreated = false;
|
||||
}
|
||||
|
||||
private void OnFocus()
|
||||
{
|
||||
// Only refresh data if UI is built
|
||||
if (rootVisualElement == null || rootVisualElement.childCount == 0)
|
||||
return;
|
||||
|
||||
RefreshAllData();
|
||||
}
|
||||
|
||||
private void OnEditorUpdate()
|
||||
{
|
||||
if (rootVisualElement == null || rootVisualElement.childCount == 0)
|
||||
return;
|
||||
|
||||
connectionSection?.UpdateConnectionStatus();
|
||||
}
|
||||
|
||||
private void RefreshAllData()
|
||||
{
|
||||
// Debounce rapid successive calls (e.g., from OnFocus being called multiple times)
|
||||
double currentTime = EditorApplication.timeSinceStartup;
|
||||
if (currentTime - lastRefreshTime < RefreshDebounceSeconds)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lastRefreshTime = currentTime;
|
||||
|
||||
connectionSection?.UpdateConnectionStatus();
|
||||
|
||||
if (MCPServiceLocator.Bridge.IsRunning)
|
||||
{
|
||||
_ = connectionSection?.VerifyBridgeConnectionAsync();
|
||||
}
|
||||
|
||||
settingsSection?.UpdatePathOverrides();
|
||||
clientConfigSection?.RefreshSelectedClient();
|
||||
}
|
||||
|
||||
private void SetupTabs()
|
||||
{
|
||||
settingsTabToggle = rootVisualElement.Q<ToolbarToggle>("settings-tab");
|
||||
toolsTabToggle = rootVisualElement.Q<ToolbarToggle>("tools-tab");
|
||||
|
||||
settingsPanel?.RemoveFromClassList("hidden");
|
||||
toolsPanel?.RemoveFromClassList("hidden");
|
||||
|
||||
if (settingsTabToggle != null)
|
||||
{
|
||||
settingsTabToggle.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (!evt.newValue)
|
||||
{
|
||||
if (toolsTabToggle != null && !toolsTabToggle.value)
|
||||
{
|
||||
settingsTabToggle.SetValueWithoutNotify(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SwitchPanel(ActivePanel.Settings);
|
||||
});
|
||||
}
|
||||
|
||||
if (toolsTabToggle != null)
|
||||
{
|
||||
toolsTabToggle.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (!evt.newValue)
|
||||
{
|
||||
if (settingsTabToggle != null && !settingsTabToggle.value)
|
||||
{
|
||||
toolsTabToggle.SetValueWithoutNotify(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SwitchPanel(ActivePanel.Tools);
|
||||
});
|
||||
}
|
||||
|
||||
var savedPanel = EditorPrefs.GetString(EditorPrefKeys.EditorWindowActivePanel, ActivePanel.Settings.ToString());
|
||||
if (!Enum.TryParse(savedPanel, out ActivePanel initialPanel))
|
||||
{
|
||||
initialPanel = ActivePanel.Settings;
|
||||
}
|
||||
|
||||
SwitchPanel(initialPanel);
|
||||
}
|
||||
|
||||
private void SwitchPanel(ActivePanel panel)
|
||||
{
|
||||
bool showSettings = panel == ActivePanel.Settings;
|
||||
|
||||
if (settingsPanel != null)
|
||||
{
|
||||
settingsPanel.style.display = showSettings ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
if (toolsPanel != null)
|
||||
{
|
||||
toolsPanel.style.display = showSettings ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
settingsTabToggle?.SetValueWithoutNotify(showSettings);
|
||||
toolsTabToggle?.SetValueWithoutNotify(!showSettings);
|
||||
|
||||
EditorPrefs.SetString(EditorPrefKeys.EditorWindowActivePanel, panel.ToString());
|
||||
}
|
||||
|
||||
internal static void RequestHealthVerification()
|
||||
{
|
||||
foreach (var window in OpenWindows)
|
||||
{
|
||||
window?.ScheduleHealthCheck();
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleHealthCheck()
|
||||
{
|
||||
EditorApplication.delayCall += async () =>
|
||||
{
|
||||
// Ensure window and components are still valid before execution
|
||||
if (this == null || connectionSection == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await connectionSection.VerifyBridgeConnectionAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log but don't crash if verification fails during cleanup
|
||||
McpLog.Warn($"Health check verification failed: {ex.Message}");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Services;
|
||||
using MCPForUnity.Editor.Windows.Components.ClientConfig;
|
||||
using MCPForUnity.Editor.Windows.Components.Connection;
|
||||
using MCPForUnity.Editor.Windows.Components.Settings;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using MCPForUnity.Editor.Constants;
|
||||
using MCPForUnity.Editor.Windows.Components.Tools;
|
||||
|
||||
namespace MCPForUnity.Editor.Windows
|
||||
{
|
||||
public class MCPForUnityEditorWindow : EditorWindow
|
||||
{
|
||||
// Section controllers
|
||||
private McpSettingsSection settingsSection;
|
||||
private McpConnectionSection connectionSection;
|
||||
private McpClientConfigSection clientConfigSection;
|
||||
private McpToolsSection toolsSection;
|
||||
|
||||
private ToolbarToggle settingsTabToggle;
|
||||
private ToolbarToggle toolsTabToggle;
|
||||
private VisualElement settingsPanel;
|
||||
private VisualElement toolsPanel;
|
||||
|
||||
private static readonly HashSet<MCPForUnityEditorWindow> OpenWindows = new();
|
||||
private bool guiCreated = false;
|
||||
private double lastRefreshTime = 0;
|
||||
private const double RefreshDebounceSeconds = 0.5;
|
||||
|
||||
private enum ActivePanel
|
||||
{
|
||||
Settings,
|
||||
Tools
|
||||
}
|
||||
|
||||
internal static void CloseAllWindows()
|
||||
{
|
||||
var windows = OpenWindows.Where(window => window != null).ToArray();
|
||||
foreach (var window in windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<MCPForUnityEditorWindow>("MCP For Unity");
|
||||
window.minSize = new Vector2(500, 600);
|
||||
}
|
||||
|
||||
// Helper to check and manage open windows from other classes
|
||||
public static bool HasAnyOpenWindow()
|
||||
{
|
||||
return OpenWindows.Count > 0;
|
||||
}
|
||||
|
||||
public static void CloseAllOpenWindows()
|
||||
{
|
||||
if (OpenWindows.Count == 0)
|
||||
return;
|
||||
|
||||
// Copy to array to avoid modifying the collection while iterating
|
||||
var arr = new MCPForUnityEditorWindow[OpenWindows.Count];
|
||||
OpenWindows.CopyTo(arr);
|
||||
foreach (var window in arr)
|
||||
{
|
||||
try
|
||||
{
|
||||
window?.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
McpLog.Warn($"Error closing MCP window: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateGUI()
|
||||
{
|
||||
// Guard against repeated CreateGUI calls (e.g., domain reloads)
|
||||
if (guiCreated)
|
||||
return;
|
||||
|
||||
string basePath = AssetPathUtility.GetMcpPackageRootPath();
|
||||
|
||||
// Load main window UXML
|
||||
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
||||
$"{basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml"
|
||||
);
|
||||
|
||||
if (visualTree == null)
|
||||
{
|
||||
McpLog.Error(
|
||||
$"Failed to load UXML at: {basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
visualTree.CloneTree(rootVisualElement);
|
||||
|
||||
// Load main window USS
|
||||
var mainStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(
|
||||
$"{basePath}/Editor/Windows/MCPForUnityEditorWindow.uss"
|
||||
);
|
||||
if (mainStyleSheet != null)
|
||||
{
|
||||
rootVisualElement.styleSheets.Add(mainStyleSheet);
|
||||
}
|
||||
|
||||
// Load common USS
|
||||
var commonStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(
|
||||
$"{basePath}/Editor/Windows/Components/Common.uss"
|
||||
);
|
||||
if (commonStyleSheet != null)
|
||||
{
|
||||
rootVisualElement.styleSheets.Add(commonStyleSheet);
|
||||
}
|
||||
|
||||
settingsPanel = rootVisualElement.Q<VisualElement>("settings-panel");
|
||||
toolsPanel = rootVisualElement.Q<VisualElement>("tools-panel");
|
||||
var settingsContainer = rootVisualElement.Q<VisualElement>("settings-container");
|
||||
var toolsContainer = rootVisualElement.Q<VisualElement>("tools-container");
|
||||
|
||||
if (settingsPanel == null || toolsPanel == null)
|
||||
{
|
||||
McpLog.Error("Failed to find tab panels in UXML");
|
||||
return;
|
||||
}
|
||||
|
||||
if (settingsContainer == null)
|
||||
{
|
||||
McpLog.Error("Failed to find settings-container in UXML");
|
||||
return;
|
||||
}
|
||||
|
||||
if (toolsContainer == null)
|
||||
{
|
||||
McpLog.Error("Failed to find tools-container in UXML");
|
||||
return;
|
||||
}
|
||||
|
||||
SetupTabs();
|
||||
|
||||
// Load and initialize Settings section
|
||||
var settingsTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
||||
$"{basePath}/Editor/Windows/Components/Settings/McpSettingsSection.uxml"
|
||||
);
|
||||
if (settingsTree != null)
|
||||
{
|
||||
var settingsRoot = settingsTree.Instantiate();
|
||||
settingsContainer.Add(settingsRoot);
|
||||
settingsSection = new McpSettingsSection(settingsRoot);
|
||||
settingsSection.OnGitUrlChanged += () =>
|
||||
clientConfigSection?.UpdateManualConfiguration();
|
||||
settingsSection.OnHttpServerCommandUpdateRequested += () =>
|
||||
connectionSection?.UpdateHttpServerCommandDisplay();
|
||||
}
|
||||
|
||||
// Load and initialize Connection section
|
||||
var connectionTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
||||
$"{basePath}/Editor/Windows/Components/Connection/McpConnectionSection.uxml"
|
||||
);
|
||||
if (connectionTree != null)
|
||||
{
|
||||
var connectionRoot = connectionTree.Instantiate();
|
||||
settingsContainer.Add(connectionRoot);
|
||||
connectionSection = new McpConnectionSection(connectionRoot);
|
||||
connectionSection.OnManualConfigUpdateRequested += () =>
|
||||
clientConfigSection?.UpdateManualConfiguration();
|
||||
}
|
||||
|
||||
// Load and initialize Client Configuration section
|
||||
var clientConfigTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
||||
$"{basePath}/Editor/Windows/Components/ClientConfig/McpClientConfigSection.uxml"
|
||||
);
|
||||
if (clientConfigTree != null)
|
||||
{
|
||||
var clientConfigRoot = clientConfigTree.Instantiate();
|
||||
settingsContainer.Add(clientConfigRoot);
|
||||
clientConfigSection = new McpClientConfigSection(clientConfigRoot);
|
||||
}
|
||||
|
||||
// Load and initialize Tools section
|
||||
var toolsTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
||||
$"{basePath}/Editor/Windows/Components/Tools/McpToolsSection.uxml"
|
||||
);
|
||||
if (toolsTree != null)
|
||||
{
|
||||
var toolsRoot = toolsTree.Instantiate();
|
||||
toolsContainer.Add(toolsRoot);
|
||||
toolsSection = new McpToolsSection(toolsRoot);
|
||||
toolsSection.Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
McpLog.Warn("Failed to load tools section UXML. Tool configuration will be unavailable.");
|
||||
}
|
||||
guiCreated = true;
|
||||
|
||||
// Initial updates
|
||||
RefreshAllData();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
OpenWindows.Add(this);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
OpenWindows.Remove(this);
|
||||
guiCreated = false;
|
||||
}
|
||||
|
||||
private void OnFocus()
|
||||
{
|
||||
// Only refresh data if UI is built
|
||||
if (rootVisualElement == null || rootVisualElement.childCount == 0)
|
||||
return;
|
||||
|
||||
RefreshAllData();
|
||||
}
|
||||
|
||||
private void OnEditorUpdate()
|
||||
{
|
||||
if (rootVisualElement == null || rootVisualElement.childCount == 0)
|
||||
return;
|
||||
|
||||
connectionSection?.UpdateConnectionStatus();
|
||||
}
|
||||
|
||||
private void RefreshAllData()
|
||||
{
|
||||
// Debounce rapid successive calls (e.g., from OnFocus being called multiple times)
|
||||
double currentTime = EditorApplication.timeSinceStartup;
|
||||
if (currentTime - lastRefreshTime < RefreshDebounceSeconds)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lastRefreshTime = currentTime;
|
||||
|
||||
connectionSection?.UpdateConnectionStatus();
|
||||
|
||||
if (MCPServiceLocator.Bridge.IsRunning)
|
||||
{
|
||||
_ = connectionSection?.VerifyBridgeConnectionAsync();
|
||||
}
|
||||
|
||||
settingsSection?.UpdatePathOverrides();
|
||||
clientConfigSection?.RefreshSelectedClient();
|
||||
}
|
||||
|
||||
private void SetupTabs()
|
||||
{
|
||||
settingsTabToggle = rootVisualElement.Q<ToolbarToggle>("settings-tab");
|
||||
toolsTabToggle = rootVisualElement.Q<ToolbarToggle>("tools-tab");
|
||||
|
||||
settingsPanel?.RemoveFromClassList("hidden");
|
||||
toolsPanel?.RemoveFromClassList("hidden");
|
||||
|
||||
if (settingsTabToggle != null)
|
||||
{
|
||||
settingsTabToggle.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (!evt.newValue)
|
||||
{
|
||||
if (toolsTabToggle != null && !toolsTabToggle.value)
|
||||
{
|
||||
settingsTabToggle.SetValueWithoutNotify(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SwitchPanel(ActivePanel.Settings);
|
||||
});
|
||||
}
|
||||
|
||||
if (toolsTabToggle != null)
|
||||
{
|
||||
toolsTabToggle.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (!evt.newValue)
|
||||
{
|
||||
if (settingsTabToggle != null && !settingsTabToggle.value)
|
||||
{
|
||||
toolsTabToggle.SetValueWithoutNotify(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SwitchPanel(ActivePanel.Tools);
|
||||
});
|
||||
}
|
||||
|
||||
var savedPanel = EditorPrefs.GetString(EditorPrefKeys.EditorWindowActivePanel, ActivePanel.Settings.ToString());
|
||||
if (!Enum.TryParse(savedPanel, out ActivePanel initialPanel))
|
||||
{
|
||||
initialPanel = ActivePanel.Settings;
|
||||
}
|
||||
|
||||
SwitchPanel(initialPanel);
|
||||
}
|
||||
|
||||
private void SwitchPanel(ActivePanel panel)
|
||||
{
|
||||
bool showSettings = panel == ActivePanel.Settings;
|
||||
|
||||
if (settingsPanel != null)
|
||||
{
|
||||
settingsPanel.style.display = showSettings ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
if (toolsPanel != null)
|
||||
{
|
||||
toolsPanel.style.display = showSettings ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
settingsTabToggle?.SetValueWithoutNotify(showSettings);
|
||||
toolsTabToggle?.SetValueWithoutNotify(!showSettings);
|
||||
|
||||
EditorPrefs.SetString(EditorPrefKeys.EditorWindowActivePanel, panel.ToString());
|
||||
}
|
||||
|
||||
internal static void RequestHealthVerification()
|
||||
{
|
||||
foreach (var window in OpenWindows)
|
||||
{
|
||||
window?.ScheduleHealthCheck();
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleHealthCheck()
|
||||
{
|
||||
EditorApplication.delayCall += async () =>
|
||||
{
|
||||
// Ensure window and components are still valid before execution
|
||||
if (this == null || connectionSection == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await connectionSection.VerifyBridgeConnectionAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log but don't crash if verification fails during cleanup
|
||||
McpLog.Warn($"Health check verification failed: {ex.Message}");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,181 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MCPForUnity.Runtime.Helpers
|
||||
//The reason for having another Runtime Utilities in additional to Editor Utilities is to avoid Editor-only dependencies in this runtime code.
|
||||
{
|
||||
public readonly struct ScreenshotCaptureResult
|
||||
{
|
||||
public ScreenshotCaptureResult(string fullPath, string assetsRelativePath, int superSize)
|
||||
{
|
||||
FullPath = fullPath;
|
||||
AssetsRelativePath = assetsRelativePath;
|
||||
SuperSize = superSize;
|
||||
}
|
||||
|
||||
public string FullPath { get; }
|
||||
public string AssetsRelativePath { get; }
|
||||
public int SuperSize { get; }
|
||||
}
|
||||
|
||||
public static class ScreenshotUtility
|
||||
{
|
||||
private const string ScreenshotsFolderName = "Screenshots";
|
||||
|
||||
public static ScreenshotCaptureResult CaptureToAssetsFolder(string fileName = null, int superSize = 1, bool ensureUniqueFileName = true)
|
||||
{
|
||||
int size = Mathf.Max(1, superSize);
|
||||
string resolvedName = BuildFileName(fileName);
|
||||
string folder = Path.Combine(Application.dataPath, ScreenshotsFolderName);
|
||||
Directory.CreateDirectory(folder);
|
||||
|
||||
string fullPath = Path.Combine(folder, resolvedName);
|
||||
if (ensureUniqueFileName)
|
||||
{
|
||||
fullPath = EnsureUnique(fullPath);
|
||||
}
|
||||
|
||||
string normalizedFullPath = fullPath.Replace('\\', '/');
|
||||
|
||||
// Use only the file name to let Unity decide the final location (per CaptureScreenshot docs).
|
||||
string captureName = Path.GetFileName(normalizedFullPath);
|
||||
ScreenCapture.CaptureScreenshot(captureName, size);
|
||||
|
||||
Debug.Log($"Screenshot requested: file='{captureName}' intendedFullPath='{normalizedFullPath}' persistentDataPath='{Application.persistentDataPath}'");
|
||||
|
||||
string projectRoot = GetProjectRootPath();
|
||||
string assetsRelativePath = normalizedFullPath;
|
||||
if (assetsRelativePath.StartsWith(projectRoot, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
assetsRelativePath = assetsRelativePath.Substring(projectRoot.Length).TrimStart('/');
|
||||
}
|
||||
|
||||
return new ScreenshotCaptureResult(
|
||||
normalizedFullPath,
|
||||
assetsRelativePath,
|
||||
size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures a screenshot from a specific camera by rendering into a temporary RenderTexture (works in Edit Mode).
|
||||
/// </summary>
|
||||
public static ScreenshotCaptureResult CaptureFromCameraToAssetsFolder(Camera camera, string fileName = null, int superSize = 1, bool ensureUniqueFileName = true)
|
||||
{
|
||||
if (camera == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(camera));
|
||||
}
|
||||
|
||||
int size = Mathf.Max(1, superSize);
|
||||
string resolvedName = BuildFileName(fileName);
|
||||
string folder = Path.Combine(Application.dataPath, ScreenshotsFolderName);
|
||||
Directory.CreateDirectory(folder);
|
||||
|
||||
string fullPath = Path.Combine(folder, resolvedName);
|
||||
if (ensureUniqueFileName)
|
||||
{
|
||||
fullPath = EnsureUnique(fullPath);
|
||||
}
|
||||
|
||||
string normalizedFullPath = fullPath.Replace('\\', '/');
|
||||
|
||||
int width = Mathf.Max(1, camera.pixelWidth > 0 ? camera.pixelWidth : Screen.width);
|
||||
int height = Mathf.Max(1, camera.pixelHeight > 0 ? camera.pixelHeight : Screen.height);
|
||||
width *= size;
|
||||
height *= size;
|
||||
|
||||
RenderTexture prevRT = camera.targetTexture;
|
||||
RenderTexture prevActive = RenderTexture.active;
|
||||
var rt = RenderTexture.GetTemporary(width, height, 24, RenderTextureFormat.ARGB32);
|
||||
try
|
||||
{
|
||||
camera.targetTexture = rt;
|
||||
camera.Render();
|
||||
|
||||
RenderTexture.active = rt;
|
||||
var tex = new Texture2D(width, height, TextureFormat.RGBA32, false);
|
||||
tex.ReadPixels(new Rect(0, 0, width, height), 0, 0);
|
||||
tex.Apply();
|
||||
|
||||
byte[] png = tex.EncodeToPNG();
|
||||
File.WriteAllBytes(normalizedFullPath, png);
|
||||
}
|
||||
finally
|
||||
{
|
||||
camera.targetTexture = prevRT;
|
||||
RenderTexture.active = prevActive;
|
||||
RenderTexture.ReleaseTemporary(rt);
|
||||
}
|
||||
|
||||
string projectRoot = GetProjectRootPath();
|
||||
string assetsRelativePath = normalizedFullPath;
|
||||
if (assetsRelativePath.StartsWith(projectRoot, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
assetsRelativePath = assetsRelativePath.Substring(projectRoot.Length).TrimStart('/');
|
||||
}
|
||||
|
||||
return new ScreenshotCaptureResult(normalizedFullPath, assetsRelativePath, size);
|
||||
}
|
||||
|
||||
private static string BuildFileName(string fileName)
|
||||
{
|
||||
string name = string.IsNullOrWhiteSpace(fileName)
|
||||
? $"screenshot-{DateTime.Now:yyyyMMdd-HHmmss}"
|
||||
: fileName.Trim();
|
||||
|
||||
name = SanitizeFileName(name);
|
||||
|
||||
if (!name.EndsWith(".png", StringComparison.OrdinalIgnoreCase) &&
|
||||
!name.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) &&
|
||||
!name.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
name += ".png";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string fileName)
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
string cleaned = new string(fileName.Select(ch => invalidChars.Contains(ch) ? '_' : ch).ToArray());
|
||||
|
||||
return string.IsNullOrWhiteSpace(cleaned) ? "screenshot" : cleaned;
|
||||
}
|
||||
|
||||
private static string EnsureUnique(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
string directory = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
string baseName = Path.GetFileNameWithoutExtension(path);
|
||||
string extension = Path.GetExtension(path);
|
||||
int counter = 1;
|
||||
|
||||
string candidate;
|
||||
do
|
||||
{
|
||||
candidate = Path.Combine(directory, $"{baseName}-{counter}{extension}");
|
||||
counter++;
|
||||
} while (File.Exists(candidate));
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private static string GetProjectRootPath()
|
||||
{
|
||||
string root = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
|
||||
root = root.Replace('\\', '/');
|
||||
if (!root.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
root += "/";
|
||||
}
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,11 +12,21 @@ from transport.legacy.unity_connection import async_send_command_with_retry
|
|||
)
|
||||
async def manage_scene(
|
||||
ctx: Context,
|
||||
action: Annotated[Literal["create", "load", "save", "get_hierarchy", "get_active", "get_build_settings"], "Perform CRUD operations on Unity scenes."],
|
||||
action: Annotated[Literal[
|
||||
"create",
|
||||
"load",
|
||||
"save",
|
||||
"get_hierarchy",
|
||||
"get_active",
|
||||
"get_build_settings",
|
||||
"screenshot",
|
||||
], "Perform CRUD operations on Unity scenes, and capture a screenshot."],
|
||||
name: Annotated[str, "Scene name."] | None = None,
|
||||
path: Annotated[str, "Scene path."] | None = None,
|
||||
build_index: Annotated[int | str,
|
||||
"Unity build index (quote as string, e.g., '0')."] | None = None,
|
||||
screenshot_file_name: Annotated[str, "Screenshot file name (optional). Defaults to timestamp when omitted."] | None = None,
|
||||
screenshot_super_size: Annotated[int | str, "Screenshot supersize multiplier (integer ≥1). Optional." ] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
# Get active instance from session state
|
||||
# Removed session_state import
|
||||
|
|
@ -39,14 +49,19 @@ async def manage_scene(
|
|||
return default
|
||||
|
||||
coerced_build_index = _coerce_int(build_index, default=None)
|
||||
coerced_super_size = _coerce_int(screenshot_super_size, default=None)
|
||||
|
||||
params = {"action": action}
|
||||
params: dict[str, Any] = {"action": action}
|
||||
if name:
|
||||
params["name"] = name
|
||||
if path:
|
||||
params["path"] = path
|
||||
if coerced_build_index is not None:
|
||||
params["buildIndex"] = coerced_build_index
|
||||
if screenshot_file_name:
|
||||
params["fileName"] = screenshot_file_name
|
||||
if coerced_super_size is not None:
|
||||
params["superSize"] = coerced_super_size
|
||||
|
||||
# Use centralized retry helper with instance routing
|
||||
response = await send_with_unity_instance(async_send_command_with_retry, unity_instance, "manage_scene", params)
|
||||
|
|
|
|||
|
|
@ -28,12 +28,8 @@ if "%PACKAGE_CACHE_PATH%"=="" (
|
|||
exit /b 1
|
||||
)
|
||||
|
||||
:: Server installation path (with default)
|
||||
echo.
|
||||
echo Server Installation Path:
|
||||
echo Default: %DEFAULT_SERVER_PATH%
|
||||
set /p "SERVER_PATH=Enter server path (or press Enter for default): "
|
||||
if "%SERVER_PATH%"=="" set "SERVER_PATH=%DEFAULT_SERVER_PATH%"
|
||||
rem Server installation path prompt disabled (server deploy skipped)
|
||||
set "SERVER_PATH="
|
||||
|
||||
:: Backup location (with default)
|
||||
echo.
|
||||
|
|
@ -54,24 +50,12 @@ if not exist "%BRIDGE_SOURCE%" (
|
|||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%SERVER_SOURCE%" (
|
||||
echo Error: Server source not found: %SERVER_SOURCE%
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%PACKAGE_CACHE_PATH%" (
|
||||
echo Error: Package cache path not found: %PACKAGE_CACHE_PATH%
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%SERVER_PATH%" (
|
||||
echo Error: Server installation path not found: %SERVER_PATH%
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: Create backup directory
|
||||
if not exist "%BACKUP_DIR%" (
|
||||
echo Creating backup directory: %BACKUP_DIR%
|
||||
|
|
@ -103,16 +87,27 @@ if exist "%PACKAGE_CACHE_PATH%\Editor" (
|
|||
)
|
||||
)
|
||||
|
||||
if exist "%SERVER_PATH%" (
|
||||
echo Backing up Python Server files...
|
||||
xcopy "%SERVER_PATH%\*" "%BACKUP_SUBDIR%\PythonServer\" /E /I /Y > nul
|
||||
if exist "%PACKAGE_CACHE_PATH%\Runtime" (
|
||||
echo Backing up Unity Runtime files...
|
||||
xcopy "%PACKAGE_CACHE_PATH%\Runtime" "%BACKUP_SUBDIR%\UnityBridge\Runtime\" /E /I /Y > nul
|
||||
if !errorlevel! neq 0 (
|
||||
echo Error: Failed to backup Python Server files
|
||||
echo Error: Failed to backup Unity Runtime files
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
rem Server backup skipped (deprecated legacy deploy)
|
||||
rem if exist "%SERVER_PATH%" (
|
||||
rem echo Backing up Python Server files...
|
||||
rem xcopy "%SERVER_PATH%\*" "%BACKUP_SUBDIR%\PythonServer\" /E /I /Y > nul
|
||||
rem if !errorlevel! neq 0 (
|
||||
rem echo Error: Failed to backup Python Server files
|
||||
rem pause
|
||||
rem exit /b 1
|
||||
rem )
|
||||
rem )
|
||||
|
||||
:: Deploy Unity Bridge
|
||||
echo.
|
||||
echo Deploying Unity Bridge code...
|
||||
|
|
@ -123,15 +118,23 @@ if !errorlevel! neq 0 (
|
|||
exit /b 1
|
||||
)
|
||||
|
||||
:: Deploy Python Server
|
||||
echo Deploying Python Server code...
|
||||
xcopy "%SERVER_SOURCE%\*" "%SERVER_PATH%\" /E /Y > nul
|
||||
echo Deploying Unity Runtime code...
|
||||
xcopy "%BRIDGE_SOURCE%\Runtime\*" "%PACKAGE_CACHE_PATH%\Runtime\" /E /Y > nul
|
||||
if !errorlevel! neq 0 (
|
||||
echo Error: Failed to deploy Python Server code
|
||||
echo Error: Failed to deploy Unity Runtime code
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
rem Deploy Python Server (disabled; server no longer deployed this way)
|
||||
rem echo Deploying Python Server code...
|
||||
rem xcopy "%SERVER_SOURCE%\*" "%SERVER_PATH%\" /E /Y > nul
|
||||
rem if !errorlevel! neq 0 (
|
||||
rem echo Error: Failed to deploy Python Server code
|
||||
rem pause
|
||||
rem exit /b 1
|
||||
rem )
|
||||
|
||||
:: Success
|
||||
echo.
|
||||
echo ===============================================
|
||||
|
|
|
|||
Loading…
Reference in New Issue