Skip to content

Commit 85eb3a0

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/master' into UNI-24204-zip-maya-integration
# Conflicts: # Assets/FbxExporters/Editor/InstallIntegration.cs # README.md
2 parents 28370cc + 71bfa4f commit 85eb3a0

38 files changed

+1769
-876
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## Executable + Temp files ##
22
*.exe
33
*.exe.*
4+
!BringToFront.exe
45
*.o
56
*.pdb
67
*.mdb

Assets/FbxExporters/Editor/ConvertToModel.cs

Lines changed: 255 additions & 170 deletions
Large diffs are not rendered by default.

Assets/FbxExporters/Editor/FbxExportSettings.cs

Lines changed: 276 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using System;
1+
using System;
22
using System.IO;
33
using UnityEditorInternal;
44
using UnityEngine;
55
using UnityEditor;
6+
using System.Collections.Generic;
67

78
namespace FbxExporters.EditorTools {
89

@@ -27,8 +28,6 @@ public override void OnInspectorGUI() {
2728
EditorGUILayout.Space ();
2829
}
2930

30-
exportSettings.weldVertices = EditorGUILayout.Toggle ("Weld Vertices:", exportSettings.weldVertices);
31-
exportSettings.embedTextures = EditorGUILayout.Toggle ("Embed Textures:", exportSettings.embedTextures);
3231
exportSettings.mayaCompatibleNames = EditorGUILayout.Toggle (
3332
new GUIContent ("Convert to Maya Compatible Naming:",
3433
"In Maya some symbols such as spaces and accents get replaced when importing an FBX " +
@@ -42,7 +41,7 @@ public override void OnInspectorGUI() {
4241

4342
exportSettings.centerObjects = EditorGUILayout.Toggle (
4443
new GUIContent("Center Objects:",
45-
"Export objects centered around the union of the bounding box of selected objects"),
44+
"Center objects around a shared root and keep their relative placement unchanged."),
4645
exportSettings.centerObjects
4746
);
4847

@@ -90,7 +89,7 @@ public override void OnInspectorGUI() {
9089
GUILayout.BeginHorizontal ();
9190

9291
GUILayout.Label (new GUIContent (
93-
"Turn Table Scene:",
92+
"Turntable Scene:",
9493
"Scene to use for reviewing models. If none, a scene will be created on review."));
9594

9695
exportSettings.turntableScene = EditorGUILayout.ObjectField (
@@ -100,13 +99,65 @@ public override void OnInspectorGUI() {
10099
GUILayout.EndHorizontal ();
101100

102101
EditorGUILayout.Space ();
103-
if (GUILayout.Button (new GUIContent ("Auto Review",
104-
"Opens turntable review of last saved prefab."))) {
105-
FbxExporters.Review.TurnTable.LastSavedModel ();
102+
103+
GUILayout.BeginHorizontal ();
104+
GUILayout.Label (new GUIContent (
105+
"Maya Application:",
106+
"Select the version of Maya where you would like to install the Unity integration."));
107+
108+
// dropdown to select Maya version to use
109+
var options = ExportSettings.GetMayaOptions();
110+
// make sure we never initially have browse selected
111+
if (exportSettings.selectedMayaApp == options.Length - 1) {
112+
exportSettings.selectedMayaApp = 0;
106113
}
114+
int oldValue = exportSettings.selectedMayaApp;
115+
exportSettings.selectedMayaApp = EditorGUILayout.Popup(exportSettings.selectedMayaApp, options);
116+
if (exportSettings.selectedMayaApp == options.Length - 1) {
117+
var ext = "exe";
118+
#if UNITY_EDITOR_OSX
119+
ext = "app";
120+
#endif
121+
string mayaPath = EditorUtility.OpenFilePanel ("Select Maya Application", ExportSettings.kDefaultAdskRoot, ext);
122+
123+
// check that the path is valid and references the maya executable
124+
if (!string.IsNullOrEmpty (mayaPath)) {
125+
if (!Path.GetFileNameWithoutExtension (mayaPath).ToLower ().Equals ("maya")) {
126+
// clicked on the wrong application, try to see if we can still find
127+
// maya in this directory.
128+
var mayaDir = new DirectoryInfo(Path.GetDirectoryName(mayaPath));
129+
#if UNITY_EDITOR_OSX
130+
var files = mayaDir.GetDirectories("*." + ext);
131+
#else
132+
var files = mayaDir.GetFiles ("*." + ext);
133+
#endif
134+
bool foundMaya = false;
135+
foreach (var file in files) {
136+
var filename = Path.GetFileNameWithoutExtension (file.Name).ToLower ();
137+
if (filename.Equals ("maya")) {
138+
mayaPath = file.FullName.Replace("\\","/");
139+
foundMaya = true;
140+
break;
141+
}
142+
}
143+
if (!foundMaya) {
144+
Debug.LogError (string.Format("Could not find Maya at: \"{0}\"", mayaDir.FullName));
145+
exportSettings.selectedMayaApp = oldValue;
146+
return;
147+
}
148+
}
149+
ExportSettings.AddMayaOption (mayaPath);
150+
Repaint ();
151+
} else {
152+
exportSettings.selectedMayaApp = oldValue;
153+
}
154+
}
155+
GUILayout.EndHorizontal ();
107156

108-
EditorGUILayout.Space ();
109-
if (GUILayout.Button ("Install Maya Integration")) {
157+
var installIntegrationContent = new GUIContent(
158+
"Install Unity Integration",
159+
"Install and configure the Unity integration for Maya so that you can import and export directly to this project.");
160+
if (GUILayout.Button (installIntegrationContent)) {
110161
FbxExporters.Editor.IntegrationsUI.InstallMayaIntegration ();
111162
}
112163

@@ -126,12 +177,33 @@ public class ExportSettings : ScriptableSingleton<ExportSettings>
126177
{
127178
public const string kDefaultSavePath = ".";
128179

180+
/// <summary>
181+
/// The path where all the different versions of Maya are installed
182+
/// by default. Depends on the platform.
183+
/// </summary>
184+
public const string kDefaultAdskRoot =
185+
#if UNITY_EDITOR_OSX
186+
"/Applications/Autodesk"
187+
#elif UNITY_EDITOR_LINUX
188+
"/usr/autodesk"
189+
#else // WINDOWS
190+
"C:/Program Files/Autodesk"
191+
#endif
192+
;
193+
129194
// Note: default values are set in LoadDefaults().
130-
public bool weldVertices;
131-
public bool embedTextures;
132195
public bool mayaCompatibleNames;
133196
public bool centerObjects;
134197

198+
/// <summary>
199+
/// In Convert-to-model, by default we delete the original object.
200+
/// This option lets you override that.
201+
/// </summary>
202+
[HideInInspector]
203+
public bool keepOriginalAfterConvert;
204+
205+
public int selectedMayaApp = 0;
206+
135207
[SerializeField]
136208
public UnityEngine.Object turntableScene;
137209

@@ -148,14 +220,201 @@ public class ExportSettings : ScriptableSingleton<ExportSettings>
148220
[SerializeField]
149221
string convertToModelSavePath;
150222

223+
// List of names in order that they appear in option list
224+
[SerializeField]
225+
private List<string> mayaOptionNames;
226+
// List of paths in order that they appear in the option list
227+
[SerializeField]
228+
private List<string> mayaOptionPaths;
229+
151230
protected override void LoadDefaults()
152231
{
153-
weldVertices = true;
154-
embedTextures = false;
155232
mayaCompatibleNames = true;
156233
centerObjects = true;
234+
keepOriginalAfterConvert = false;
157235
convertToModelSavePath = kDefaultSavePath;
158236
turntableScene = null;
237+
mayaOptionPaths = null;
238+
mayaOptionNames = null;
239+
}
240+
241+
/// <summary>
242+
/// Increments the name if there is a duplicate in MayaAppOptions dictionary.
243+
/// </summary>
244+
/// <returns>The unique name.</returns>
245+
/// <param name="name">Name.</param>
246+
private static string GetUniqueName(string name){
247+
if (!instance.mayaOptionNames.Contains(name)) {
248+
return name;
249+
}
250+
var format = "{1} ({0})";
251+
int index = 1;
252+
// try extracting the current index from the name and incrementing it
253+
var result = System.Text.RegularExpressions.Regex.Match(name, @"\((?<number>\d+?)\)$");
254+
if (result != null) {
255+
var number = result.Groups["number"].Value;
256+
int tempIndex;
257+
if (int.TryParse (number, out tempIndex)) {
258+
var indexOfNumber = name.LastIndexOf (number);
259+
format = name.Remove (indexOfNumber, number.Length).Insert (indexOfNumber, "{0}");
260+
index = tempIndex+1;
261+
}
262+
}
263+
264+
string uniqueName = null;
265+
do {
266+
uniqueName = string.Format (format, index, name);
267+
index++;
268+
} while (instance.mayaOptionNames.Contains(name));
269+
270+
return uniqueName;
271+
}
272+
273+
/// <summary>
274+
/// Find Maya installations at default install path.
275+
/// Add results to given dictionary.
276+
///
277+
/// If MAYA_LOCATION is set, add this to the list as well.
278+
/// </summary>
279+
private static void FindMayaInstalls() {
280+
instance.mayaOptionPaths = new List<string> ();
281+
instance.mayaOptionNames = new List<string> ();
282+
var mayaOptionName = instance.mayaOptionNames;
283+
var mayaOptionPath = instance.mayaOptionPaths;
284+
285+
// If the location is given by the environment, use it.
286+
var location = System.Environment.GetEnvironmentVariable ("MAYA_LOCATION");
287+
if (!string.IsNullOrEmpty(location)) {
288+
location = location.TrimEnd('/');
289+
mayaOptionPath.Add (GetMayaExePath (location.Replace ("\\", "/")));
290+
mayaOptionName.Add ("MAYA_LOCATION");
291+
}
292+
293+
// List that directory and find the right version:
294+
// either the newest version, or the exact version we wanted.
295+
var adskRoot = new System.IO.DirectoryInfo(kDefaultAdskRoot);
296+
foreach(var productDir in adskRoot.GetDirectories()) {
297+
var product = productDir.Name;
298+
299+
// Only accept those that start with 'maya' in either case.
300+
if (!product.StartsWith("maya", StringComparison.InvariantCultureIgnoreCase)) {
301+
continue;
302+
}
303+
// Reject MayaLT -- it doesn't have plugins.
304+
if (product.StartsWith("mayalt", StringComparison.InvariantCultureIgnoreCase)) {
305+
continue;
306+
}
307+
string version = product.Substring ("maya".Length);
308+
mayaOptionPath.Add (GetMayaExePath (productDir.FullName.Replace ("\\", "/")));
309+
mayaOptionName.Add (GetUniqueName("Maya " + version));
310+
}
311+
}
312+
313+
/// <summary>
314+
/// Gets the maya exe at Maya install location.
315+
/// </summary>
316+
/// <returns>The maya exe path.</returns>
317+
/// <param name="location">Location of Maya install.</param>
318+
private static string GetMayaExePath(string location)
319+
{
320+
#if UNITY_EDITOR_OSX
321+
// MAYA_LOCATION on mac is set by Autodesk to be the
322+
// Contents directory. But let's make it easier on people
323+
// and allow just having it be the app bundle or a
324+
// directory that holds the app bundle.
325+
if (location.EndsWith(".app/Contents")) {
326+
return location + "/MacOS/Maya";
327+
} else if (location.EndsWith(".app")) {
328+
return location + "/Contents/MacOS/Maya";
329+
} else {
330+
return location + "/Maya.app/Contents/MacOS/Maya";
331+
}
332+
#elif UNITY_EDITOR_LINUX
333+
return location + "/bin/maya";
334+
#else // WINDOWS
335+
return location + "/bin/maya.exe";
336+
#endif
337+
}
338+
339+
public static GUIContent[] GetMayaOptions(){
340+
if (instance.mayaOptionNames == null ||
341+
instance.mayaOptionNames.Count != instance.mayaOptionPaths.Count ||
342+
instance.mayaOptionNames.Count == 0) {
343+
FindMayaInstalls ();
344+
}
345+
346+
// remove options that no longer exist
347+
List<int> toDelete = new List<int>();
348+
for(int i = 0; i < instance.mayaOptionPaths.Count; i++) {
349+
var mayaPath = instance.mayaOptionPaths [i];
350+
if (!File.Exists (mayaPath)) {
351+
if (i == instance.selectedMayaApp) {
352+
instance.selectedMayaApp = 0;
353+
}
354+
instance.mayaOptionNames.RemoveAt (i);
355+
toDelete.Add (i);
356+
}
357+
}
358+
foreach (var index in toDelete) {
359+
instance.mayaOptionPaths.RemoveAt (index);
360+
}
361+
362+
GUIContent[] optionArray = new GUIContent[instance.mayaOptionPaths.Count+1];
363+
for(int i = 0; i < instance.mayaOptionPaths.Count; i++){
364+
optionArray [i] = new GUIContent(
365+
instance.mayaOptionNames[i],
366+
instance.mayaOptionPaths[i]
367+
);
368+
}
369+
optionArray [optionArray.Length - 1] = new GUIContent("Browse...");
370+
371+
return optionArray;
372+
}
373+
374+
public static void AddMayaOption(string newOption){
375+
// on OSX we get a path ending in .app, which is not quite the exe
376+
#if UNITY_EDITOR_OSX
377+
newOption = GetMayaExePath(newOption);
378+
#endif
379+
380+
var mayaOptionPaths = instance.mayaOptionPaths;
381+
if (mayaOptionPaths.Contains(newOption)) {
382+
instance.selectedMayaApp = mayaOptionPaths.IndexOf (newOption);
383+
return;
384+
}
385+
// get the version
386+
var version = AskMayaVersion(newOption);
387+
instance.mayaOptionNames.Add (GetUniqueName("Maya "+version));
388+
mayaOptionPaths.Add (newOption);
389+
instance.selectedMayaApp = mayaOptionPaths.Count - 1;
390+
}
391+
392+
/// <summary>
393+
/// Ask the version number by running maya.
394+
/// </summary>
395+
static string AskMayaVersion(string exePath) {
396+
System.Diagnostics.Process myProcess = new System.Diagnostics.Process();
397+
myProcess.StartInfo.FileName = exePath;
398+
myProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
399+
myProcess.StartInfo.CreateNoWindow = true;
400+
myProcess.StartInfo.UseShellExecute = false;
401+
myProcess.StartInfo.RedirectStandardOutput = true;
402+
myProcess.StartInfo.Arguments = "-v";
403+
myProcess.EnableRaisingEvents = true;
404+
myProcess.Start();
405+
string resultString = myProcess.StandardOutput.ReadToEnd();
406+
myProcess.WaitForExit();
407+
408+
// Output is like: Maya 2018, Cut Number 201706261615
409+
// We want the stuff after 'Maya ' and before the comma.
410+
// TODO: less brittle! Consider also the mel command "about -version".
411+
var commaIndex = resultString.IndexOf(',');
412+
return resultString.Substring(0, commaIndex).Substring("Maya ".Length);
413+
}
414+
415+
public static string GetSelectedMayaPath()
416+
{
417+
return instance.mayaOptionPaths [instance.selectedMayaApp];
159418
}
160419

161420
public static string GetTurnTableSceneName(){
@@ -215,6 +474,9 @@ public static void SetRelativeSavePath(string newPath) {
215474
/// </summary>
216475
public static string ConvertToAssetRelativePath(string fullPathInAssets, bool requireSubdirectory = true)
217476
{
477+
if (!Path.IsPathRooted(fullPathInAssets)) {
478+
fullPathInAssets = Path.GetFullPath(fullPathInAssets);
479+
}
218480
var relativePath = GetRelativePath(Application.dataPath, fullPathInAssets);
219481
if (requireSubdirectory && relativePath.StartsWith("..")) {
220482
if (relativePath.Length == 2 || relativePath[2] == '/') {

0 commit comments

Comments
 (0)