@mflrevan/ucp 0.4.4 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bridge/com.ucp.bridge/CHANGELOG.md +145 -0
- package/bridge/com.ucp.bridge/CHANGELOG.md.meta +7 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +583 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Bridge.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +425 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs +355 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs +233 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +26 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +31 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs +527 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs +141 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +326 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs +209 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +409 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +354 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +93 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs +503 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +188 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +260 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs +1679 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +563 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +166 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +318 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs +125 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs +104 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +227 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +240 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs +611 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs +53 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs +80 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +358 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs +37 -0
- package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Scripts.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef +16 -0
- package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Editor.meta +8 -0
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +14 -0
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Runtime.meta +8 -0
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +1085 -0
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef +12 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Tests/Editor.meta +8 -0
- package/bridge/com.ucp.bridge/Tests.meta +8 -0
- package/bridge/com.ucp.bridge/package.json +27 -0
- package/bridge/com.ucp.bridge/package.json.meta +7 -0
- package/package.json +2 -2
- package/scripts/install.js +4 -6
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
using System.Collections.Generic;
|
|
2
|
+
using System.Linq;
|
|
3
|
+
using UnityEditor;
|
|
4
|
+
using UnityEditor.SceneManagement;
|
|
5
|
+
using UnityEngine;
|
|
6
|
+
using UnityEngine.SceneManagement;
|
|
7
|
+
|
|
8
|
+
namespace UCP.Bridge
|
|
9
|
+
{
|
|
10
|
+
[InitializeOnLoad]
|
|
11
|
+
public static class SceneChangeTracker
|
|
12
|
+
{
|
|
13
|
+
private sealed class TrackedSceneChange
|
|
14
|
+
{
|
|
15
|
+
public int? InstanceId;
|
|
16
|
+
public string Name;
|
|
17
|
+
public HashSet<string> Components = new();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private static readonly Dictionary<int, Dictionary<string, TrackedSceneChange>> s_changesByScene = new();
|
|
21
|
+
|
|
22
|
+
static SceneChangeTracker()
|
|
23
|
+
{
|
|
24
|
+
Undo.postprocessModifications += OnPostprocessModifications;
|
|
25
|
+
Undo.undoRedoPerformed += OnUndoRedoPerformed;
|
|
26
|
+
EditorSceneManager.sceneSaved += ClearScene;
|
|
27
|
+
EditorSceneManager.sceneOpened += (scene, mode) => ClearScene(scene);
|
|
28
|
+
EditorSceneManager.newSceneCreated += (scene, setup, mode) => ClearScene(scene);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public static void RecordGameObjectChange(GameObject gameObject, string componentName)
|
|
32
|
+
{
|
|
33
|
+
if (gameObject == null)
|
|
34
|
+
return;
|
|
35
|
+
|
|
36
|
+
RecordSceneChange(gameObject.scene, gameObject.GetInstanceID(), gameObject.name, componentName);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public static void RecordDeletedObject(Scene scene, int instanceId, string name, string componentName)
|
|
40
|
+
{
|
|
41
|
+
RecordSceneChange(scene, instanceId, name, componentName);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public static void RecordSceneSettingChange(Scene scene, string changeName)
|
|
45
|
+
{
|
|
46
|
+
RecordSceneChange(scene, null, scene.name, changeName);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static Dictionary<string, object> DescribeActiveSceneChanges(int maxEntries)
|
|
50
|
+
{
|
|
51
|
+
return DescribeSceneChanges(SceneManager.GetActiveScene(), maxEntries);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public static Dictionary<string, object> DescribeSceneChanges(Scene scene, int maxEntries)
|
|
55
|
+
{
|
|
56
|
+
var modifications = new List<object>();
|
|
57
|
+
var omittedCount = 0;
|
|
58
|
+
|
|
59
|
+
if (scene.IsValid() && s_changesByScene.TryGetValue(scene.handle, out var trackedChanges))
|
|
60
|
+
{
|
|
61
|
+
var ordered = trackedChanges.Values
|
|
62
|
+
.OrderBy(change => change.InstanceId.HasValue ? 0 : 1)
|
|
63
|
+
.ThenBy(change => change.Name)
|
|
64
|
+
.ToList();
|
|
65
|
+
|
|
66
|
+
omittedCount = Mathf.Max(ordered.Count - maxEntries, 0);
|
|
67
|
+
foreach (var change in ordered.Take(maxEntries))
|
|
68
|
+
{
|
|
69
|
+
modifications.Add(new Dictionary<string, object>
|
|
70
|
+
{
|
|
71
|
+
["instanceId"] = change.InstanceId.HasValue ? change.InstanceId.Value : null,
|
|
72
|
+
["name"] = change.Name,
|
|
73
|
+
["components"] = change.Components.OrderBy(component => component).Cast<object>().ToList()
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (scene.isDirty && modifications.Count == 0)
|
|
79
|
+
{
|
|
80
|
+
modifications.Add(new Dictionary<string, object>
|
|
81
|
+
{
|
|
82
|
+
["instanceId"] = null,
|
|
83
|
+
["name"] = scene.name,
|
|
84
|
+
["components"] = new List<object> { "UnknownChange" }
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return new Dictionary<string, object>
|
|
89
|
+
{
|
|
90
|
+
["name"] = scene.name,
|
|
91
|
+
["path"] = scene.path,
|
|
92
|
+
["isDirty"] = scene.isDirty,
|
|
93
|
+
["isLoaded"] = scene.isLoaded,
|
|
94
|
+
["modifications"] = modifications,
|
|
95
|
+
["omittedCount"] = omittedCount
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public static void ClearScene(Scene scene)
|
|
100
|
+
{
|
|
101
|
+
if (!scene.IsValid())
|
|
102
|
+
return;
|
|
103
|
+
|
|
104
|
+
s_changesByScene.Remove(scene.handle);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private static UndoPropertyModification[] OnPostprocessModifications(UndoPropertyModification[] modifications)
|
|
108
|
+
{
|
|
109
|
+
foreach (var modification in modifications)
|
|
110
|
+
{
|
|
111
|
+
RecordTarget(modification.currentValue.target);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return modifications;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private static void OnUndoRedoPerformed()
|
|
118
|
+
{
|
|
119
|
+
var activeScene = SceneManager.GetActiveScene();
|
|
120
|
+
if (activeScene.IsValid() && activeScene.isDirty)
|
|
121
|
+
{
|
|
122
|
+
RecordSceneSettingChange(activeScene, "Undo/Redo");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private static void RecordTarget(Object target)
|
|
127
|
+
{
|
|
128
|
+
if (target is Component component && component.gameObject != null)
|
|
129
|
+
{
|
|
130
|
+
RecordGameObjectChange(component.gameObject, component.GetType().Name);
|
|
131
|
+
}
|
|
132
|
+
else if (target is GameObject gameObject)
|
|
133
|
+
{
|
|
134
|
+
RecordGameObjectChange(gameObject, "GameObject");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private static void RecordSceneChange(Scene scene, int? instanceId, string name, string componentName)
|
|
139
|
+
{
|
|
140
|
+
if (!scene.IsValid() || !scene.isLoaded)
|
|
141
|
+
return;
|
|
142
|
+
|
|
143
|
+
if (!s_changesByScene.TryGetValue(scene.handle, out var sceneChanges))
|
|
144
|
+
{
|
|
145
|
+
sceneChanges = new Dictionary<string, TrackedSceneChange>();
|
|
146
|
+
s_changesByScene[scene.handle] = sceneChanges;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
var key = instanceId.HasValue ? instanceId.Value.ToString() : $"scene::{name}";
|
|
150
|
+
if (!sceneChanges.TryGetValue(key, out var trackedChange))
|
|
151
|
+
{
|
|
152
|
+
trackedChange = new TrackedSceneChange
|
|
153
|
+
{
|
|
154
|
+
InstanceId = instanceId,
|
|
155
|
+
Name = string.IsNullOrWhiteSpace(name) ? "Unnamed" : name
|
|
156
|
+
};
|
|
157
|
+
sceneChanges[key] = trackedChange;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!string.IsNullOrWhiteSpace(componentName))
|
|
161
|
+
{
|
|
162
|
+
trackedChange.Components.Add(componentName);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
using System.Collections.Generic;
|
|
2
|
+
using UnityEditor;
|
|
3
|
+
using UnityEditor.SceneManagement;
|
|
4
|
+
using UnityEngine;
|
|
5
|
+
using UnityEngine.SceneManagement;
|
|
6
|
+
|
|
7
|
+
namespace UCP.Bridge
|
|
8
|
+
{
|
|
9
|
+
public static class SceneController
|
|
10
|
+
{
|
|
11
|
+
public static void Register(CommandRouter router)
|
|
12
|
+
{
|
|
13
|
+
router.Register("scene/list", HandleList);
|
|
14
|
+
router.Register("scene/load", HandleLoad);
|
|
15
|
+
router.Register("scene/active", HandleActive);
|
|
16
|
+
router.Register("scene/save-active", HandleSaveActive);
|
|
17
|
+
router.Register("scene/dirty-summary", HandleDirtySummary);
|
|
18
|
+
router.Register("scene/focus", HandleFocus);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private static object HandleList(string paramsJson)
|
|
22
|
+
{
|
|
23
|
+
var scenes = new List<object>();
|
|
24
|
+
|
|
25
|
+
for (int i = 0; i < EditorBuildSettings.scenes.Length; i++)
|
|
26
|
+
{
|
|
27
|
+
var s = EditorBuildSettings.scenes[i];
|
|
28
|
+
scenes.Add(new Dictionary<string, object>
|
|
29
|
+
{
|
|
30
|
+
["index"] = i,
|
|
31
|
+
["path"] = s.path,
|
|
32
|
+
["enabled"] = s.enabled,
|
|
33
|
+
["guid"] = s.guid.ToString()
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return new Dictionary<string, object> { ["scenes"] = scenes };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private static object HandleLoad(string paramsJson)
|
|
41
|
+
{
|
|
42
|
+
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
43
|
+
if (p == null || !p.TryGetValue("path", out var pathObj))
|
|
44
|
+
throw new System.ArgumentException("Missing 'path' parameter");
|
|
45
|
+
|
|
46
|
+
var path = pathObj.ToString();
|
|
47
|
+
var saveDirtyScenes = GetBoolParam(p, "saveDirtyScenes", true);
|
|
48
|
+
var discardUntitled = GetBoolParam(p, "discardUntitled", true);
|
|
49
|
+
|
|
50
|
+
if (EditorApplication.isPlaying)
|
|
51
|
+
{
|
|
52
|
+
SceneManager.LoadScene(path);
|
|
53
|
+
}
|
|
54
|
+
else
|
|
55
|
+
{
|
|
56
|
+
SaveDirtyScenesIfRequested(saveDirtyScenes, discardUntitled);
|
|
57
|
+
EditorSceneManager.OpenScene(path, OpenSceneMode.Single);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return new { status = "ok", loaded = path };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private static bool GetBoolParam(Dictionary<string, object> parameters, string key, bool defaultValue)
|
|
64
|
+
{
|
|
65
|
+
if (parameters != null && parameters.TryGetValue(key, out var valueObj) && valueObj is bool value)
|
|
66
|
+
return value;
|
|
67
|
+
|
|
68
|
+
return defaultValue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private static void SaveDirtyScenesIfRequested(bool saveDirtyScenes, bool discardUntitled)
|
|
72
|
+
{
|
|
73
|
+
if (!saveDirtyScenes)
|
|
74
|
+
return;
|
|
75
|
+
|
|
76
|
+
var requiresUntitledDiscard = false;
|
|
77
|
+
|
|
78
|
+
for (var index = 0; index < SceneManager.sceneCount; index++)
|
|
79
|
+
{
|
|
80
|
+
var scene = SceneManager.GetSceneAt(index);
|
|
81
|
+
if (!scene.isLoaded || !scene.isDirty)
|
|
82
|
+
continue;
|
|
83
|
+
|
|
84
|
+
if (string.IsNullOrEmpty(scene.path))
|
|
85
|
+
{
|
|
86
|
+
if (!discardUntitled)
|
|
87
|
+
throw new System.InvalidOperationException("Dirty untitled scene cannot be auto-saved. Retry with discardUntitled=true.");
|
|
88
|
+
|
|
89
|
+
requiresUntitledDiscard = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!EditorSceneManager.SaveScene(scene))
|
|
94
|
+
throw new System.InvalidOperationException($"Failed to auto-save dirty scene: {scene.path}");
|
|
95
|
+
|
|
96
|
+
SceneChangeTracker.ClearScene(scene);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (requiresUntitledDiscard)
|
|
100
|
+
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private static object HandleSaveActive(string paramsJson)
|
|
104
|
+
{
|
|
105
|
+
var scene = SceneManager.GetActiveScene();
|
|
106
|
+
if (!scene.IsValid() || !scene.isLoaded)
|
|
107
|
+
throw new System.InvalidOperationException("No active loaded scene to save");
|
|
108
|
+
|
|
109
|
+
if (string.IsNullOrEmpty(scene.path))
|
|
110
|
+
throw new System.InvalidOperationException("Active scene is untitled and cannot be auto-saved");
|
|
111
|
+
|
|
112
|
+
if (!scene.isDirty)
|
|
113
|
+
{
|
|
114
|
+
return new Dictionary<string, object>
|
|
115
|
+
{
|
|
116
|
+
["status"] = "ok",
|
|
117
|
+
["saved"] = false,
|
|
118
|
+
["name"] = scene.name,
|
|
119
|
+
["path"] = scene.path
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!EditorSceneManager.SaveScene(scene))
|
|
124
|
+
throw new System.InvalidOperationException($"Failed to save active scene: {scene.path}");
|
|
125
|
+
|
|
126
|
+
SceneChangeTracker.ClearScene(scene);
|
|
127
|
+
|
|
128
|
+
return new Dictionary<string, object>
|
|
129
|
+
{
|
|
130
|
+
["status"] = "ok",
|
|
131
|
+
["saved"] = true,
|
|
132
|
+
["name"] = scene.name,
|
|
133
|
+
["path"] = scene.path
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private static object HandleDirtySummary(string paramsJson)
|
|
138
|
+
{
|
|
139
|
+
var parameters = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
140
|
+
var maxEntries = 8;
|
|
141
|
+
if (parameters != null && parameters.TryGetValue("maxEntries", out var valueObj) && valueObj != null)
|
|
142
|
+
maxEntries = Mathf.Max(1, System.Convert.ToInt32(valueObj));
|
|
143
|
+
|
|
144
|
+
return SceneChangeTracker.DescribeActiveSceneChanges(maxEntries);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private static object HandleActive(string paramsJson)
|
|
148
|
+
{
|
|
149
|
+
var scene = SceneManager.GetActiveScene();
|
|
150
|
+
return new Dictionary<string, object>
|
|
151
|
+
{
|
|
152
|
+
["name"] = scene.name,
|
|
153
|
+
["path"] = scene.path,
|
|
154
|
+
["buildIndex"] = scene.buildIndex,
|
|
155
|
+
["isDirty"] = scene.isDirty,
|
|
156
|
+
["isLoaded"] = scene.isLoaded,
|
|
157
|
+
["rootCount"] = scene.rootCount
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private static object HandleFocus(string paramsJson)
|
|
162
|
+
{
|
|
163
|
+
var parameters = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
164
|
+
if (parameters == null || !parameters.TryGetValue("instanceId", out var idObj))
|
|
165
|
+
throw new System.ArgumentException("Missing 'instanceId' parameter");
|
|
166
|
+
|
|
167
|
+
var instanceId = System.Convert.ToInt32(idObj);
|
|
168
|
+
var target = FindGameObject(instanceId);
|
|
169
|
+
var bounds = CalculateFocusBounds(target);
|
|
170
|
+
var sceneView = SceneView.lastActiveSceneView ?? EditorWindow.GetWindow<SceneView>();
|
|
171
|
+
|
|
172
|
+
if (sceneView == null)
|
|
173
|
+
throw new System.InvalidOperationException("Unable to open Scene view");
|
|
174
|
+
|
|
175
|
+
sceneView.Show();
|
|
176
|
+
sceneView.Focus();
|
|
177
|
+
Selection.activeGameObject = target;
|
|
178
|
+
|
|
179
|
+
var focusPoint = bounds.center;
|
|
180
|
+
var focusSize = Mathf.Max(bounds.extents.magnitude * 2f, 1f);
|
|
181
|
+
var axis = TryReadAxis(parameters);
|
|
182
|
+
|
|
183
|
+
if (axis.HasValue)
|
|
184
|
+
{
|
|
185
|
+
var normalizedAxis = axis.Value.normalized;
|
|
186
|
+
var rotation = Quaternion.LookRotation(-normalizedAxis, SelectUpVector(normalizedAxis));
|
|
187
|
+
sceneView.LookAtDirect(focusPoint, rotation, focusSize);
|
|
188
|
+
}
|
|
189
|
+
else
|
|
190
|
+
{
|
|
191
|
+
sceneView.LookAtDirect(focusPoint, sceneView.rotation, focusSize);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
sceneView.Repaint();
|
|
195
|
+
SceneView.RepaintAll();
|
|
196
|
+
|
|
197
|
+
return new Dictionary<string, object>
|
|
198
|
+
{
|
|
199
|
+
["status"] = "ok",
|
|
200
|
+
["instanceId"] = instanceId,
|
|
201
|
+
["name"] = target.name,
|
|
202
|
+
["pivot"] = VectorToList(sceneView.pivot),
|
|
203
|
+
["cameraPosition"] = VectorToList(sceneView.camera.transform.position),
|
|
204
|
+
["cameraRotationEuler"] = VectorToList(sceneView.camera.transform.rotation.eulerAngles),
|
|
205
|
+
["size"] = sceneView.size,
|
|
206
|
+
["axis"] = axis.HasValue ? VectorToList(axis.Value.normalized) : null
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private static Vector3? TryReadAxis(Dictionary<string, object> parameters)
|
|
211
|
+
{
|
|
212
|
+
if (parameters == null || !parameters.TryGetValue("axis", out var axisObj) || axisObj == null)
|
|
213
|
+
return null;
|
|
214
|
+
|
|
215
|
+
if (axisObj is not List<object> values || values.Count != 3)
|
|
216
|
+
throw new System.ArgumentException("axis must be an array of exactly three numeric values");
|
|
217
|
+
|
|
218
|
+
var axis = new Vector3(
|
|
219
|
+
System.Convert.ToSingle(values[0]),
|
|
220
|
+
System.Convert.ToSingle(values[1]),
|
|
221
|
+
System.Convert.ToSingle(values[2]));
|
|
222
|
+
|
|
223
|
+
if (axis.sqrMagnitude < 0.0001f)
|
|
224
|
+
throw new System.ArgumentException("axis must not be the zero vector");
|
|
225
|
+
|
|
226
|
+
return axis;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private static Vector3 SelectUpVector(Vector3 axis)
|
|
230
|
+
{
|
|
231
|
+
if (Mathf.Abs(Vector3.Dot(axis, Vector3.up)) > 0.98f)
|
|
232
|
+
return Vector3.forward;
|
|
233
|
+
|
|
234
|
+
return Vector3.up;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private static Bounds CalculateFocusBounds(GameObject target)
|
|
238
|
+
{
|
|
239
|
+
var hasBounds = false;
|
|
240
|
+
var bounds = new Bounds(target.transform.position, Vector3.one);
|
|
241
|
+
|
|
242
|
+
foreach (var renderer in target.GetComponentsInChildren<Renderer>())
|
|
243
|
+
{
|
|
244
|
+
if (!hasBounds)
|
|
245
|
+
{
|
|
246
|
+
bounds = renderer.bounds;
|
|
247
|
+
hasBounds = true;
|
|
248
|
+
}
|
|
249
|
+
else
|
|
250
|
+
{
|
|
251
|
+
bounds.Encapsulate(renderer.bounds);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
foreach (var collider in target.GetComponentsInChildren<Collider>())
|
|
256
|
+
{
|
|
257
|
+
if (!hasBounds)
|
|
258
|
+
{
|
|
259
|
+
bounds = collider.bounds;
|
|
260
|
+
hasBounds = true;
|
|
261
|
+
}
|
|
262
|
+
else
|
|
263
|
+
{
|
|
264
|
+
bounds.Encapsulate(collider.bounds);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!hasBounds)
|
|
269
|
+
bounds = new Bounds(target.transform.position, Vector3.one);
|
|
270
|
+
|
|
271
|
+
return bounds;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private static GameObject FindGameObject(int instanceId)
|
|
275
|
+
{
|
|
276
|
+
var direct = EditorUtility.EntityIdToObject(instanceId) as GameObject;
|
|
277
|
+
if (direct != null)
|
|
278
|
+
return direct;
|
|
279
|
+
|
|
280
|
+
for (var index = 0; index < SceneManager.sceneCount; index++)
|
|
281
|
+
{
|
|
282
|
+
var scene = SceneManager.GetSceneAt(index);
|
|
283
|
+
if (!scene.isLoaded)
|
|
284
|
+
continue;
|
|
285
|
+
|
|
286
|
+
foreach (var root in scene.GetRootGameObjects())
|
|
287
|
+
{
|
|
288
|
+
var found = FindInHierarchy(root, instanceId);
|
|
289
|
+
if (found != null)
|
|
290
|
+
return found;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
throw new System.ArgumentException($"GameObject not found: {instanceId}");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private static GameObject FindInHierarchy(GameObject gameObject, int instanceId)
|
|
298
|
+
{
|
|
299
|
+
if (gameObject.GetInstanceID() == instanceId)
|
|
300
|
+
return gameObject;
|
|
301
|
+
|
|
302
|
+
foreach (Transform child in gameObject.transform)
|
|
303
|
+
{
|
|
304
|
+
var found = FindInHierarchy(child.gameObject, instanceId);
|
|
305
|
+
if (found != null)
|
|
306
|
+
return found;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private static List<object> VectorToList(Vector3 value)
|
|
313
|
+
{
|
|
314
|
+
return new List<object> { value.x, value.y, value.z };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
using UnityEditor;
|
|
5
|
+
|
|
6
|
+
namespace UCP.Bridge
|
|
7
|
+
{
|
|
8
|
+
public static class ScreenshotController
|
|
9
|
+
{
|
|
10
|
+
public static void Register(CommandRouter router)
|
|
11
|
+
{
|
|
12
|
+
router.Register("screenshot", HandleScreenshot);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private static object HandleScreenshot(string paramsJson)
|
|
16
|
+
{
|
|
17
|
+
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
18
|
+
int width = 1920, height = 1080;
|
|
19
|
+
string view = "game";
|
|
20
|
+
|
|
21
|
+
if (p != null)
|
|
22
|
+
{
|
|
23
|
+
if (p.TryGetValue("width", out var w)) width = Convert.ToInt32(w);
|
|
24
|
+
if (p.TryGetValue("height", out var h)) height = Convert.ToInt32(h);
|
|
25
|
+
if (p.TryGetValue("view", out var v)) view = v?.ToString() ?? "game";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Clamp dimensions for safety
|
|
29
|
+
width = Mathf.Clamp(width, 64, 7680);
|
|
30
|
+
height = Mathf.Clamp(height, 64, 4320);
|
|
31
|
+
|
|
32
|
+
byte[] png;
|
|
33
|
+
|
|
34
|
+
if (view == "game")
|
|
35
|
+
{
|
|
36
|
+
png = CaptureGameView(width, height);
|
|
37
|
+
}
|
|
38
|
+
else
|
|
39
|
+
{
|
|
40
|
+
// Scene view capture
|
|
41
|
+
png = CaptureSceneView(width, height);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (png == null || png.Length == 0)
|
|
45
|
+
{
|
|
46
|
+
throw new Exception("Screenshot capture failed - no camera available");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
string base64 = Convert.ToBase64String(png);
|
|
50
|
+
|
|
51
|
+
return new Dictionary<string, object>
|
|
52
|
+
{
|
|
53
|
+
["width"] = width,
|
|
54
|
+
["height"] = height,
|
|
55
|
+
["format"] = "png",
|
|
56
|
+
["encoding"] = "base64",
|
|
57
|
+
["data"] = base64,
|
|
58
|
+
["size"] = png.Length
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private static byte[] CaptureGameView(int width, int height)
|
|
63
|
+
{
|
|
64
|
+
var camera = Camera.main;
|
|
65
|
+
if (camera == null)
|
|
66
|
+
{
|
|
67
|
+
// Try to find any camera
|
|
68
|
+
camera = UnityEngine.Object.FindAnyObjectByType<Camera>();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (camera == null)
|
|
72
|
+
throw new Exception("No camera found in scene");
|
|
73
|
+
|
|
74
|
+
var rt = new RenderTexture(width, height, 24);
|
|
75
|
+
var prevTarget = camera.targetTexture;
|
|
76
|
+
|
|
77
|
+
camera.targetTexture = rt;
|
|
78
|
+
camera.Render();
|
|
79
|
+
|
|
80
|
+
RenderTexture.active = rt;
|
|
81
|
+
var texture = new Texture2D(width, height, TextureFormat.RGB24, false);
|
|
82
|
+
texture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
|
|
83
|
+
texture.Apply();
|
|
84
|
+
|
|
85
|
+
camera.targetTexture = prevTarget;
|
|
86
|
+
RenderTexture.active = null;
|
|
87
|
+
|
|
88
|
+
byte[] png = texture.EncodeToPNG();
|
|
89
|
+
|
|
90
|
+
UnityEngine.Object.DestroyImmediate(rt);
|
|
91
|
+
UnityEngine.Object.DestroyImmediate(texture);
|
|
92
|
+
|
|
93
|
+
return png;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private static byte[] CaptureSceneView(int width, int height)
|
|
97
|
+
{
|
|
98
|
+
var sceneView = SceneView.lastActiveSceneView;
|
|
99
|
+
if (sceneView == null || sceneView.camera == null)
|
|
100
|
+
throw new Exception("No active Scene view");
|
|
101
|
+
|
|
102
|
+
var camera = sceneView.camera;
|
|
103
|
+
var rt = new RenderTexture(width, height, 24);
|
|
104
|
+
var prevTarget = camera.targetTexture;
|
|
105
|
+
|
|
106
|
+
camera.targetTexture = rt;
|
|
107
|
+
camera.Render();
|
|
108
|
+
|
|
109
|
+
RenderTexture.active = rt;
|
|
110
|
+
var texture = new Texture2D(width, height, TextureFormat.RGB24, false);
|
|
111
|
+
texture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
|
|
112
|
+
texture.Apply();
|
|
113
|
+
|
|
114
|
+
camera.targetTexture = prevTarget;
|
|
115
|
+
RenderTexture.active = null;
|
|
116
|
+
|
|
117
|
+
byte[] png = texture.EncodeToPNG();
|
|
118
|
+
|
|
119
|
+
UnityEngine.Object.DestroyImmediate(rt);
|
|
120
|
+
UnityEngine.Object.DestroyImmediate(texture);
|
|
121
|
+
|
|
122
|
+
return png;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|