Skip to content

Commit 1fc24b3

Browse files
author
Benoit Hudson
committed
uni-40523: defining all the cases and making them work
The spec wasn't clear about what would happen in which case. Now it should be, and unit tests make sure it's all happening. Particular changes: - now Convert returns the prefab, not the instance. That's because sometimes there isn't an instance. I had to change a few unit tests. - if what we're converting is an fbx or an instance of an fbx, use it. That was mostly implemented before. - if what we're converting is a prefab, update the prefab - added unit tests to each part so we can make sure the complicated logic is properly implemented - probably fixed a bug in file paths for prefabs in some cases; I didn't test whether I could actually trigger it I also changed the ExporterTestBase ExportToFbx function to return the asset. Almost every call to that function wanted the asset rather than what it used to return which is its filename.
1 parent 64f5e8b commit 1fc24b3

10 files changed

+374
-163
lines changed

Assets/FbxExporters/Editor/ConvertToModel.cs

Lines changed: 127 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -94,40 +94,84 @@ public static GameObject[] CreateInstantiatedModelPrefab (
9494
/// <summary>
9595
/// Convert one object (and the hierarchy below it) to an auto-updating prefab.
9696
///
97-
/// This returns a new object; the converted object may be modified or destroyed.
97+
/// Returns the prefab asset that's linked to the fbx.
9898
///
99-
/// This refreshes the asset database.
100-
///
101-
/// If 'toConvert' is an asset, we return a prefab.
102-
/// If 'toConvert' is an object in the scene, we return an instance of the prefab.
99+
/// If 'toConvert' is:
100+
/// (1) an object in the scene, then the hierarchy will be exported and a new auto-updating prefab created pointing to the new fbx
101+
/// (2) the root of an fbx asset, or the root of an instance of an fbx asset, then a new auto-updating prefab will be created pointing to the existing fbx
102+
/// (3) a prefab asset (but *not* if it's an instance of a prefab), then a new fbx asset will be exported and the prefab will be made to auto-update from the new fbx
103103
/// </summary>
104-
/// <returns>The instance that replaces 'toConvert' in the scene, or the prefab that replaces 'toConvert' in the project.</returns>
105-
/// <param name="toConvert">GameObject hierarchy to replace with a prefab.</param>
106-
/// <param name="fbxFullPath">Absolute platform-specific path to
107-
/// the fbx file. May be null, in which case we construct a unique
108-
/// filename from the object name and the
109-
/// directoryFullPath.</param>
110-
/// <param name="directoryFullPath">Absolute platform-specific path
111-
/// to a directory in which to put the fbx file. Ignored if
112-
/// fbxFullPath is specified. May be null, in which case we use the
113-
/// export settings.</param>
104+
/// <returns>The prefab asset linked to an fbx file.</returns>
105+
/// <param name="toConvert">Object to convert.</param>
106+
/// <param name="fbxFullPath">Absolute platform-specific path to the fbx file. If the file already exists, it will be overwritten. May be null, in which case we construct a unique filename. Ignored if 'toConvert' is an fbx asset or is an instance of one.</param>
107+
/// <param name="fbxDirectoryFullPath">Absolute platform-specific path to a directory in which to put the fbx file under a unique filename. May be null, in which case we use the export settings. Ignored if 'fbxFullPath' is specified. Ignored if 'toConvert' is an fbx asset or an instance of one.</param>
108+
/// <param name="prefabFullPath">Absolute platform-specific path to the prefab file. If the file already exists, it will be overwritten. May be null, in which case we construct a unique filename. Ignored if 'toConvert' is a prefab asset.</param>
109+
/// <param name="prefabDirectoryFullPath">Absolute platform-specific path to a directory in which to put the prefab file under a unique filename. May be null, in which case we use the export settings. Ignored if 'prefabFullPath' is specified. Ignored if 'toConvert' is a prefab asset.
114110
public static GameObject Convert (
115111
GameObject toConvert,
116112
string fbxDirectoryFullPath = null,
117113
string fbxFullPath = null,
118114
string prefabDirectoryFullPath = null,
119115
string prefabFullPath = null,
120-
EditorTools.IExportOptions exportOptions = null
121-
)
116+
EditorTools.IExportOptions exportOptions = null)
122117
{
123118
// If we selected the something that's already backed by an FBX, don't export.
124-
var mainAsset = GetModelPrefabOrNull(toConvert);
125-
if (mainAsset) {
126-
var mainAssetRelPath = AssetDatabase.GetAssetPath(mainAsset);
127-
return SetupFbxPrefab(toConvert, mainAsset, mainAssetRelPath);
119+
var mainAsset = GetOrCreateFbxAsset (toConvert, fbxDirectoryFullPath, fbxFullPath, exportOptions);
120+
121+
// Get 'toConvert' into an editable state. We can't edit
122+
// assets, and toConvert might be an asset.
123+
var toConvertInstance = GetOrCreateInstance (toConvert);
124+
125+
// Set it up to track the FbxPrefab.
126+
SetupFbxPrefab (toConvertInstance, mainAsset);
127+
128+
// Now get 'toConvertInstance' into a prefab. If toConvert is already a prefab,
129+
// this is equivalent to an 'apply' ; if it's not, we're creating a new prefab.
130+
var prefab = ApplyOrCreatePrefab (toConvertInstance, prefabDirectoryFullPath, prefabFullPath);
131+
132+
if (toConvertInstance == toConvert) {
133+
// If we were converting an instance, the caller expects
134+
// the instance to have the name it got saved with.
135+
var path = AssetDatabase.GetAssetPath (prefab);
136+
var filename = Path.GetFileNameWithoutExtension (path);
137+
toConvert.name = filename;
138+
} else {
139+
// If 'toConvert' was an asset, we created a temp
140+
// instance to add the component; destroy it.
141+
Object.DestroyImmediate(toConvertInstance);
128142
}
143+
return prefab;
144+
}
129145

130-
// Otherwise, do export.
146+
/// <summary>
147+
/// Check whether GetOrCreateFbxAsset will be exporting an fbx file, or reusing one.
148+
/// </summary>
149+
public static bool WillExportFbx(GameObject go) {
150+
return GetFbxAssetOrNull(go) == null;
151+
}
152+
153+
/// <summary>
154+
/// Return an FBX asset that corresponds to 'toConvert'.
155+
///
156+
/// If 'toConvert' is the root of an FBX asset, return it.
157+
///
158+
/// If it's an instance in a scene the points to the root of an FBX asset, return that asset.
159+
///
160+
/// Otherwise, export according to the paths and options, and return the new asset.
161+
/// </summary>
162+
/// <param name="toConvert">GameObject for which we want an fbx asset</param>
163+
/// <param name="fbxDirectoryFullPath">Export will choose an appropriate filename in this directory. Ignored if fbxFullPath is set. Ignored if toConvert is an fbx asset or an instance of an fbx.</param>
164+
/// <param name="fbxDirectoryFullPath">Export will create this file. Overrides fbxDirectoryFullPath. Ignored if toConvert is an fbx asset or an instance of an fbx.</param>
165+
/// <returns>The root of a model prefab asset.</returns>
166+
public static GameObject GetOrCreateFbxAsset(GameObject toConvert,
167+
string fbxDirectoryFullPath = null,
168+
string fbxFullPath = null,
169+
EditorTools.IExportOptions exportOptions = null)
170+
{
171+
var mainAsset = GetFbxAssetOrNull(toConvert);
172+
if (mainAsset) {
173+
return mainAsset;
174+
}
131175

132176
if (string.IsNullOrEmpty(fbxFullPath)) {
133177
// Generate a unique filename.
@@ -172,88 +216,103 @@ public static GameObject Convert (
172216
// Copy the mesh/materials from the FBX
173217
UpdateFromSourceRecursive (toConvert, unityMainAsset);
174218

219+
return unityMainAsset;
220+
}
221+
222+
/// <summary>
223+
/// Return a gameobject in the scene that corresponds to 'toConvert'.
224+
///
225+
/// If toConvert is an asset, instantiate it and retain the link.
226+
/// If it's a gameobject in the scene, return it.
227+
/// </summary>
228+
public static GameObject GetOrCreateInstance(GameObject toConvert)
229+
{
230+
switch(PrefabUtility.GetPrefabType(toConvert)) {
231+
case PrefabType.Prefab:
232+
case PrefabType.ModelPrefab:
233+
return PrefabUtility.InstantiatePrefab(toConvert) as GameObject;
234+
default:
235+
return toConvert;
236+
}
237+
}
238+
239+
/// <summary>
240+
/// Check whether ApplyOrCreatePrefab will be creating a new prefab, or updating one.
241+
/// </summary>
242+
public static bool WillCreatePrefab(GameObject go) {
243+
return PrefabUtility.GetPrefabType(go) != PrefabType.PrefabInstance;
244+
}
245+
246+
/// <summary>
247+
/// Create a prefab from 'instance', or apply 'instance' to its
248+
/// prefab if it's already an instance of a prefab.
249+
///
250+
/// Return the new or updated prefab.
251+
/// </summary>
252+
public static GameObject ApplyOrCreatePrefab(GameObject instance,
253+
string prefabDirectoryFullPath = null,
254+
string prefabFullPath = null)
255+
{
256+
if(PrefabUtility.GetPrefabType(instance) == PrefabType.PrefabInstance) {
257+
// Apply: there's already a prefab.
258+
return PrefabUtility.ReplacePrefab(instance, PrefabUtility.GetPrefabParent(instance));
259+
}
260+
261+
// Otherwise, create a new prefab. First choose its filename/path.
175262
if (string.IsNullOrEmpty(prefabFullPath)) {
176263
// Generate a unique filename.
177264
if (string.IsNullOrEmpty (prefabDirectoryFullPath)) {
178-
fbxDirectoryFullPath = FbxExporters.EditorTools.ExportSettings.GetPrefabAbsoluteSavePath();
265+
prefabDirectoryFullPath = FbxExporters.EditorTools.ExportSettings.GetPrefabAbsoluteSavePath();
179266
} else {
180-
fbxDirectoryFullPath = Path.GetFullPath (prefabDirectoryFullPath);
267+
prefabDirectoryFullPath = Path.GetFullPath (prefabDirectoryFullPath);
181268
}
182-
var prefabBasename = ModelExporter.ConvertToValidFilename (toConvert.name + ".prefab");
269+
var prefabBasename = ModelExporter.ConvertToValidFilename (instance.name + ".prefab");
183270

184271
prefabFullPath = Path.Combine (prefabDirectoryFullPath, prefabBasename);
185272
if (File.Exists (prefabFullPath)) {
186273
prefabFullPath = IncrementFileName (prefabDirectoryFullPath, prefabFullPath);
187274
}
188275
}
189276
var prefabProjectRelativePath = EditorTools.ExportSettings.GetProjectRelativePath (prefabFullPath);
277+
var prefabFileName = Path.ChangeExtension(prefabProjectRelativePath, ".prefab");
190278

191-
// Create the FbxPrefab component and the prefab asset itself.
192-
toConvert = SetupFbxPrefab (toConvert, unityMainAsset, prefabProjectRelativePath);
193-
toConvert.name = Path.GetFileNameWithoutExtension (fbxFullPath);
194-
195-
return toConvert;
279+
var prefab = PrefabUtility.CreatePrefab(prefabFileName, instance, ReplacePrefabOptions.ConnectToPrefab);
280+
if (!prefab) {
281+
throw new System.Exception(string.Format("Failed to create prefab asset in [{0}]", prefabFileName));
282+
}
283+
return prefab;
196284
}
197285

198286
/// <summary>
199-
/// Connect 'toConvert' to the main asset and create a prefab on disk.
287+
/// Connect 'toSetUp' to the main asset.
200288
///
201-
/// If 'toConvert' is an asset, we return a prefab.
202-
/// If 'toConvert' is an object in the scene, we return an instance of the prefab.
289+
/// Adds the FbxPrefab components and links it. Does not actually create or update a prefab asset.
203290
/// </summary>
204-
/// <param name="toConvert">Hierarchy to convert.</param>
291+
/// <param name="toSetUp">Instance in the scene that we want to link to the fbx asset.</param>
205292
/// <param name="unityMainAsset">Main asset in the FBX.</param>
206-
/// <param name="projectRelativePath">Fbx project relative path.</param>
207-
public static GameObject SetupFbxPrefab(GameObject toConvert, GameObject unityMainAsset, string projectRelativePath)
293+
public static void SetupFbxPrefab(GameObject toSetUp, GameObject unityMainAsset)
208294
{
209-
if (PrefabUtility.GetPrefabType(toConvert) == PrefabType.ModelPrefab) {
210-
// We can't directly edit the prefab. Instead we must:
211-
// Instantiate, set up the instance, then destroy the
212-
// instance but return the prefab.
213-
GameObject instance = null;
214-
try {
215-
// Object.Instantiate so that we can get just the branch we care about.
216-
instance = Object.Instantiate(toConvert);
217-
instance = SetupFbxPrefab(instance, unityMainAsset, projectRelativePath);
218-
return PrefabUtility.GetPrefabParent(instance) as GameObject;
219-
} finally {
220-
if (instance) { Object.DestroyImmediate(instance); }
221-
}
222-
}
223-
224295
// Set up the FbxPrefab component so it will auto-update.
225296
// Make sure to delete whatever FbxPrefab history we had.
226-
var fbxPrefab = toConvert.GetComponent<FbxPrefab>();
297+
var fbxPrefab = toSetUp.GetComponent<FbxPrefab>();
227298
if (fbxPrefab) {
228299
Object.DestroyImmediate(fbxPrefab);
229300
}
230-
fbxPrefab = toConvert.AddComponent<FbxPrefab>();
301+
fbxPrefab = toSetUp.AddComponent<FbxPrefab>();
231302
var fbxPrefabUtility = new FbxPrefabAutoUpdater.FbxPrefabUtility (fbxPrefab);
232303
fbxPrefabUtility.SetSourceModel(unityMainAsset);
233-
234-
// Create a prefab from the instantiated and componentized unityGO.
235-
var prefabFileName = Path.ChangeExtension(projectRelativePath, ".prefab");
236-
var prefab = PrefabUtility.CreatePrefab(prefabFileName, toConvert, ReplacePrefabOptions.ConnectToPrefab);
237-
if (!prefab) {
238-
var fbxFullPath = AssetDatabase.GetAssetPath(unityMainAsset);
239-
throw new System.Exception(
240-
string.Format("Failed to create prefab asset in [{0}] from fbx [{1}]",
241-
prefabFileName, fbxFullPath));
242-
}
243-
244-
return toConvert;
245304
}
246305

247306
/// <summary>
248-
/// Returns the prefab on disk corresponding to the same hierarchy as is selected.
307+
/// Returns the fbx asset on disk corresponding to the same hierarchy as is selected.
249308
///
250309
/// Returns go if go is the root of a model prefab.
251310
/// Returns the prefab parent of go if it's the root of a model prefab.
252311
/// Returns null in all other circumstances.
253312
/// </summary>
254313
/// <returns>The root of a model prefab asset, or null.</returns>
255-
/// <param name="go">Go.</param>
256-
public static GameObject GetModelPrefabOrNull(GameObject go){
314+
/// <param name="go">A gameobject either in the scene or in the assets folder.</param>
315+
public static GameObject GetFbxAssetOrNull(GameObject go) {
257316
// Children of model prefab instances will also have "model prefab instance"
258317
// as their prefab type, so it is important that it is the root that is selected.
259318
//

Assets/FbxExporters/Editor/ConvertToPrefabEditorWindow.cs

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ protected void SetGameObjectsToConvert(IEnumerable<GameObject> toConvert){
4848
if (ToExport.Length == 1) {
4949
var go = ModelExporter.GetGameObject (ToExport [0]);
5050
// check if the GameObject is a model instance, use as default filename and path if it is
51-
var mainAsset = ConvertToModel.GetModelPrefabOrNull(go);
51+
var mainAsset = ConvertToModel.GetFbxAssetOrNull(go);
5252
if (!mainAsset) {
5353
// Use the game object's name
5454
m_prefabFileName = go.name;
@@ -109,28 +109,17 @@ protected override bool Export ()
109109
if (ToExport.Length == 1) {
110110
var go = ModelExporter.GetGameObject (ToExport [0]);
111111

112-
if (!OverwriteExistingFile (prefabPath)) {
113-
return false;
114-
}
115-
116-
// Only create the prefab (no FBX export) if we have selected the root of a model prefab instance.
117-
var mainAsset = ConvertToModel.GetModelPrefabOrNull(go);
118-
if (mainAsset) {
119-
// don't re-export fbx
120-
// create prefab out of model instance in scene, link to existing fbx
121-
var mainAssetRelPath = AssetDatabase.GetAssetPath(mainAsset);
122-
var mainAssetAbsPath = System.IO.Directory.GetParent(Application.dataPath) + "/" + mainAssetRelPath;
123-
var relPrefabPath = ExportSettings.GetProjectRelativePath (prefabPath);
124-
125-
if (string.Equals(System.IO.Path.GetFullPath(fbxPath), System.IO.Path.GetFullPath(mainAssetAbsPath))) {
126-
ConvertToModel.SetupFbxPrefab(go, mainAsset, relPrefabPath);
127-
return true;
112+
// Check if we'll be clobbering files. If so, warn the user
113+
// first and let them cancel out.
114+
if (ConvertToModel.WillCreatePrefab(go)) {
115+
if (!OverwriteExistingFile (prefabPath)) {
116+
return false;
128117
}
129118
}
130-
131-
// check if file already exists, give a warning if it does
132-
if (!OverwriteExistingFile (fbxPath)) {
133-
return false;
119+
if (ConvertToModel.WillExportFbx(go)) {
120+
if (!OverwriteExistingFile (fbxPath)) {
121+
return false;
122+
}
134123
}
135124

136125
ConvertToModel.Convert (
@@ -140,6 +129,7 @@ protected override bool Export ()
140129
}
141130

142131
foreach (var obj in ToExport) {
132+
// Convert, automatically choosing a file path that won't clobber any existing files.
143133
var go = ModelExporter.GetGameObject (obj);
144134
ConvertToModel.Convert (
145135
go, fbxDirectoryFullPath: fbxDirPath, prefabDirectoryFullPath: prefabDirPath, exportOptions: ExportSettings.instance.convertToPrefabSettings.info

Assets/FbxExporters/Editor/FbxPrefabAutoUpdater.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,18 @@ public static bool OnValidateMenuItem()
246246
/// <summary>
247247
/// Launch the manual update of the linked prefab specified
248248
/// </summary>
249-
public static void UpdateLinkedPrefab(GameObject prefabInstance)
249+
public static void UpdateLinkedPrefab(GameObject prefabOrInstance)
250250
{
251-
GameObject prefab = UnityEditor.PrefabUtility.GetPrefabParent(prefabInstance) as GameObject;
252-
if (!prefab)
253-
{
251+
// Find the prefab, bail if this is neither a prefab nor an instance.
252+
GameObject prefab;
253+
switch(PrefabUtility.GetPrefabType(prefabOrInstance)) {
254+
case PrefabType.Prefab:
255+
prefab = prefabOrInstance;
256+
break;
257+
case PrefabType.PrefabInstance:
258+
prefab = PrefabUtility.GetPrefabParent(prefabOrInstance) as GameObject;
259+
break;
260+
default:
254261
return;
255262
}
256263

0 commit comments

Comments
 (0)