Skip to content

Commit ece3035

Browse files
authored
Merge pull request #1056 from jacklaplante/codex/fix-prefab-stage-save
fix: save prefab stage edits using the correct prefab-stage workflow
2 parents 84bd4cc + da0bd24 commit ece3035

2 files changed

Lines changed: 110 additions & 9 deletions

File tree

MCPForUnity/Editor/Tools/Prefabs/ManagePrefabs.cs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,15 +1348,12 @@ private static object SavePrefabStage()
13481348
return new ErrorResponse("Not currently in prefab editing mode. Open a prefab stage first with open_prefab_stage.");
13491349
}
13501350

1351-
string prefabPath = prefabStage.assetPath;
1352-
EditorSceneManager.MarkSceneDirty(prefabStage.scene);
1353-
bool saved = EditorSceneManager.SaveScene(prefabStage.scene);
1354-
if (!saved)
1351+
if (!TrySavePrefabStage(prefabStage, out string prefabPath, out string errorMessage))
13551352
{
1356-
return new ErrorResponse($"Failed to save prefab stage for '{prefabPath}'. The file may be read-only or the disk may be full.");
1353+
return new ErrorResponse(errorMessage);
13571354
}
13581355

1359-
return new SuccessResponse($"Saved prefab stage changes for '{prefabPath}'.", new { prefabPath, saved });
1356+
return new SuccessResponse($"Saved prefab stage changes for '{prefabPath}'.", new { prefabPath, saved = true });
13601357
}
13611358
catch (Exception e)
13621359
{
@@ -1376,10 +1373,9 @@ private static object ClosePrefabStage(bool saveBeforeClose = false)
13761373

13771374
if (saveBeforeClose)
13781375
{
1379-
var saveResult = SavePrefabStage();
1380-
if (saveResult is ErrorResponse)
1376+
if (!TrySavePrefabStage(prefabStage, out _, out string errorMessage))
13811377
{
1382-
return saveResult;
1378+
return new ErrorResponse(errorMessage);
13831379
}
13841380
}
13851381

@@ -1393,6 +1389,31 @@ private static object ClosePrefabStage(bool saveBeforeClose = false)
13931389
}
13941390
}
13951391

1392+
private static bool TrySavePrefabStage(PrefabStage prefabStage, out string prefabPath, out string errorMessage)
1393+
{
1394+
prefabPath = prefabStage.assetPath;
1395+
errorMessage = null;
1396+
1397+
if (prefabStage.prefabContentsRoot == null)
1398+
{
1399+
errorMessage = $"Failed to save prefab stage for '{prefabPath}'. The prefab contents root is missing.";
1400+
return false;
1401+
}
1402+
1403+
bool saved;
1404+
PrefabUtility.SaveAsPrefabAsset(prefabStage.prefabContentsRoot, prefabPath, out saved);
1405+
if (!saved)
1406+
{
1407+
errorMessage = $"Failed to save prefab stage for '{prefabPath}'. The file may be read-only or the disk may be full.";
1408+
return false;
1409+
}
1410+
1411+
prefabStage.ClearDirtiness();
1412+
AssetDatabase.SaveAssets();
1413+
AssetDatabase.Refresh();
1414+
return true;
1415+
}
1416+
13961417
#endregion
13971418
}
13981419
}

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManagePrefabsStageTests.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,86 @@ public void OpenPrefabStage_PrefabPathTakesPrecedenceOverPath()
151151
}
152152
}
153153

154+
[Test]
155+
public void SavePrefabStage_PersistsPrefabContentChanges()
156+
{
157+
string prefabPath = CreateTestPrefab("SaveStageRoot");
158+
159+
try
160+
{
161+
AssertOpenPrefabStage(prefabPath);
162+
163+
var stage = PrefabStageUtility.GetCurrentPrefabStage();
164+
Assert.IsNotNull(stage, "Expected prefab stage to be open.");
165+
166+
var child = new GameObject("SavedChild");
167+
child.transform.SetParent(stage.prefabContentsRoot.transform, false);
168+
169+
var saveResult = ToJObject(ManagePrefabs.HandleCommand(new JObject
170+
{
171+
["action"] = "save_prefab_stage"
172+
}));
173+
174+
Assert.IsTrue(saveResult.Value<bool>("success"), $"Expected save to succeed but got: {saveResult}");
175+
Assert.AreEqual(prefabPath, saveResult["data"].Value<string>("prefabPath"));
176+
177+
StageUtility.GoToMainStage();
178+
179+
GameObject reloaded = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
180+
Assert.IsNotNull(reloaded.transform.Find("SavedChild"), "Saved prefab should contain the new child after save_prefab_stage.");
181+
}
182+
finally
183+
{
184+
StageUtility.GoToMainStage();
185+
SafeDeleteAsset(prefabPath);
186+
}
187+
}
188+
189+
[Test]
190+
public void ClosePrefabStage_SaveBeforeClose_PersistsChanges()
191+
{
192+
string prefabPath = CreateTestPrefab("CloseSaveRoot");
193+
194+
try
195+
{
196+
AssertOpenPrefabStage(prefabPath);
197+
198+
var stage = PrefabStageUtility.GetCurrentPrefabStage();
199+
Assert.IsNotNull(stage, "Expected prefab stage to be open.");
200+
201+
var child = new GameObject("CloseSavedChild");
202+
child.transform.SetParent(stage.prefabContentsRoot.transform, false);
203+
204+
var closeResult = ToJObject(ManagePrefabs.HandleCommand(new JObject
205+
{
206+
["action"] = "close_prefab_stage",
207+
["saveBeforeClose"] = true
208+
}));
209+
210+
Assert.IsTrue(closeResult.Value<bool>("success"), $"Expected close with save to succeed but got: {closeResult}");
211+
Assert.IsNull(PrefabStageUtility.GetCurrentPrefabStage(), "Prefab stage should be closed after close_prefab_stage.");
212+
213+
GameObject reloaded = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
214+
Assert.IsNotNull(reloaded.transform.Find("CloseSavedChild"), "Saved prefab should contain the new child after close_prefab_stage(saveBeforeClose: true).");
215+
}
216+
finally
217+
{
218+
StageUtility.GoToMainStage();
219+
SafeDeleteAsset(prefabPath);
220+
}
221+
}
222+
223+
private static void AssertOpenPrefabStage(string prefabPath)
224+
{
225+
var openResult = ToJObject(ManagePrefabs.HandleCommand(new JObject
226+
{
227+
["action"] = "open_prefab_stage",
228+
["prefabPath"] = prefabPath
229+
}));
230+
231+
Assert.IsTrue(openResult.Value<bool>("success"), $"Expected open to succeed but got: {openResult}");
232+
}
233+
154234
private static string CreateTestPrefab(string rootName)
155235
{
156236
string prefabPath = Path.Combine(TempDirectory, $"{rootName}.prefab").Replace('\\', '/');

0 commit comments

Comments
 (0)