@@ -28,12 +28,12 @@ public override void OnInspectorGUI() {
28
28
"In Maya some symbols such as spaces and accents get replaced when importing an FBX " +
29
29
"(e.g. \" foo bar\" becomes \" fooFBXASC032bar\" ). " +
30
30
"On export, convert the names of GameObjects so they are Maya compatible." +
31
- ( exportSettings . mayaCompatibleNames ? "" :
31
+ ( exportSettings . mayaCompatibleNames ? "" :
32
32
"\n \n WARNING: Disabling this feature may result in lost material connections," +
33
33
" and unexpected character replacements in Maya." )
34
34
) ,
35
35
exportSettings . mayaCompatibleNames ) ;
36
-
36
+
37
37
exportSettings . centerObjects = EditorGUILayout . Toggle (
38
38
new GUIContent ( "Center Objects:" ,
39
39
"Export objects centered around the union of the bounding box of selected objects" ) ,
@@ -108,99 +108,178 @@ public class ExportSettings : ScriptableSingleton<ExportSettings>
108
108
109
109
/// <summary>
110
110
/// The path where Convert To Model will save the new fbx and prefab.
111
- /// This is relative to the Application.dataPath
111
+ /// This is relative to the Application.dataPath ; it uses '/' as the
112
+ /// separator on all platforms.
112
113
/// </summary>
113
114
public static string GetRelativeSavePath ( ) {
114
115
var relativePath = instance . convertToModelSavePath ;
115
116
if ( string . IsNullOrEmpty ( relativePath ) ) {
116
117
relativePath = kDefaultSavePath ;
117
118
}
118
- return NormalizeRelativePath ( relativePath ) ;
119
+ return NormalizePath ( relativePath , isRelative : true ) ;
119
120
}
120
121
121
122
/// <summary>
122
123
/// The path where Convert To Model will save the new fbx and prefab.
123
- /// This is an absolute path
124
+ /// This is an absolute path, with platform separators.
124
125
/// </summary>
125
126
public static string GetAbsoluteSavePath ( ) {
126
127
var relativePath = GetRelativeSavePath ( ) ;
127
128
var absolutePath = Path . Combine ( Application . dataPath , relativePath ) ;
128
- return Path . GetFullPath ( absolutePath ) ;
129
+ return NormalizePath ( absolutePath , isRelative : false ,
130
+ separator : Path . DirectorySeparatorChar ) ;
129
131
}
130
132
131
133
/// <summary>
132
134
/// Set the path where Convert To Model will save the new fbx and prefab.
133
135
/// This is interpreted as being relative to the Application.dataPath
134
136
/// </summary>
135
137
public static void SetRelativeSavePath ( string newPath ) {
136
- instance . convertToModelSavePath = newPath
137
- . Replace ( '\\ ' , '/' )
138
- . TrimEnd ( Path . DirectorySeparatorChar ) ;
138
+ instance . convertToModelSavePath = NormalizePath ( newPath , isRelative : true ) ;
139
139
}
140
140
141
141
/// <summary>
142
142
/// Convert an absolute path into a relative path like what you would
143
143
/// get from GetRelativeSavePath.
144
144
///
145
- /// Returns an empty string if the path is invalid.
146
- /// The path uses platform path separators, and no trailing or leading
147
- /// slashes.
145
+ /// This uses '/' as the path separator.
148
146
/// </summary>
149
147
public static string ConvertToAssetRelativePath ( string fullPathInAssets )
150
148
{
151
149
return GetRelativePath ( Application . dataPath , fullPathInAssets ) ;
152
150
}
153
151
154
- private static string GetRelativePath ( string fromDir , string toDir ) {
152
+ /// <summary>
153
+ /// Compute how to get from 'fromDir' to 'toDir' via a relative path.
154
+ /// </summary>
155
+ public static string GetRelativePath ( string fromDir , string toDir ,
156
+ char separator = '/' )
157
+ {
155
158
// https://stackoverflow.com/questions/275689/how-to-get-relative-path-from-absolute-path
156
- // With fixes to handle that fromDir and toDir are both directories (not files).
157
- if ( String . IsNullOrEmpty ( fromDir ) ) throw new ArgumentNullException ( "fromDir" ) ;
158
- if ( String . IsNullOrEmpty ( toDir ) ) throw new ArgumentNullException ( "toDir" ) ;
159
-
160
- // MakeRelativeUri assumes the path is a file unless it ends with a
161
- // path separator, so add one. Having multiple in a row is no problem.
162
- fromDir += Path . DirectorySeparatorChar ;
163
- toDir += Path . DirectorySeparatorChar ;
164
-
165
- // Workaround for https://bugzilla.xamarin.com/show_bug.cgi?id=5921
166
- fromDir += Path . DirectorySeparatorChar ;
167
-
168
- Uri fromUri = new Uri ( fromDir ) ;
169
- Uri toUri = new Uri ( toDir ) ;
170
-
171
- if ( fromUri . Scheme != toUri . Scheme ) { return null ; } // path can't be made relative.
159
+ // Except... the MakeRelativeUri that ships with Unity is buggy.
160
+ // e.g. https://bugzilla.xamarin.com/show_bug.cgi?id=5921
161
+ // among other bugs. So we roll our own.
162
+
163
+ // Normalize the paths, assuming they're absolute paths (if they
164
+ // aren't, they get normalized as relative paths)
165
+ fromDir = NormalizePath ( fromDir , isRelative : false ) ;
166
+ toDir = NormalizePath ( toDir , isRelative : false ) ;
167
+
168
+ // Break them into path components.
169
+ var fromDirs = fromDir . Split ( '/' ) ;
170
+ var toDirs = toDir . Split ( '/' ) ;
171
+
172
+ // Find the least common ancestor
173
+ int lca = - 1 ;
174
+ for ( int i = 0 , n = System . Math . Min ( fromDirs . Length , toDirs . Length ) ; i < n ; ++ i ) {
175
+ if ( fromDirs [ i ] != toDirs [ i ] ) { break ; }
176
+ lca = i ;
177
+ }
172
178
173
- Uri relativeUri = fromUri . MakeRelativeUri ( toUri ) ;
174
- String relativePath = Uri . UnescapeDataString ( relativeUri . ToString ( ) ) ;
179
+ // Step up from the fromDir to the lca, then down from lca to the toDir.
180
+ // If from = /a/b/c/d
181
+ // and to = /a/b/e/f/g
182
+ // Then we need to go up 2 and down 3.
183
+ var nStepsUp = ( fromDirs . Length - 1 ) - lca ;
184
+ var nStepsDown = ( toDirs . Length - 1 ) - lca ;
185
+ if ( nStepsUp + nStepsDown == 0 ) {
186
+ return "." ;
187
+ }
175
188
176
- if ( string . IsNullOrEmpty ( relativePath ) ) {
177
- // The relative path is empty if it's the same directory.
178
- relativePath = "." ;
189
+ var relDirs = new string [ nStepsUp + nStepsDown ] ;
190
+ for ( int i = 0 ; i < nStepsUp ; ++ i ) {
191
+ relDirs [ i ] = ".." ;
192
+ }
193
+ for ( int i = 0 ; i < nStepsDown ; ++ i ) {
194
+ relDirs [ nStepsUp + i ] = toDirs [ lca + 1 + i ] ;
179
195
}
180
196
181
- return NormalizeRelativePath ( relativePath ) ;
197
+ return string . Join ( "" + separator , relDirs ) ;
182
198
}
183
199
184
- private static string NormalizeRelativePath ( string relativePath )
200
+ /// <summary>
201
+ /// Normalize a path, cleaning up path separators, resolving '.' and
202
+ /// '..', removing duplicate and trailing path separators, etc.
203
+ ///
204
+ /// If the path passed in is a relative path, we remove leading path separators.
205
+ /// If it's an absolute path we don't.
206
+ ///
207
+ /// If you claim the path is absolute but actually it's relative, we
208
+ /// treat it as a relative path.
209
+ /// </summary>
210
+ public static string NormalizePath ( string path , bool isRelative ,
211
+ char separator = '/' )
185
212
{
186
- // The empty path is the current directory.
187
- if ( string . IsNullOrEmpty ( relativePath ) ) {
188
- relativePath = "." ;
213
+ // Use slashes to simplify the code (we're going to clobber them all anyway).
214
+ path = path . Replace ( '\\ ' , '/' ) ;
215
+
216
+ // If we're supposed to be an absolute path, but we're actually a
217
+ // relative path, ignore the 'isRelative' flag.
218
+ if ( ! isRelative && ! Path . IsPathRooted ( path ) ) {
219
+ isRelative = true ;
189
220
}
190
221
191
- // Normalize to the platform path separator.
192
- relativePath = relativePath . Replace (
193
- Path . AltDirectorySeparatorChar ,
194
- Path . DirectorySeparatorChar ) ;
222
+ // Build up a list of directory items.
223
+ var dirs = path . Split ( '/' ) ;
224
+
225
+ // Modify dirs in-place, reading from readIndex and remembering
226
+ // what index we've written to.
227
+ int lastWriteIndex = - 1 ;
228
+ for ( int readIndex = 0 , n = dirs . Length ; readIndex < n ; ++ readIndex ) {
229
+ var dir = dirs [ readIndex ] ;
230
+
231
+ // Skip duplicate path separators.
232
+ if ( dir == "" ) {
233
+ // Skip if it's not a leading path separator.
234
+ if ( lastWriteIndex >= 0 ) {
235
+ continue ; }
236
+
237
+ // Also skip if it's leading and we have a relative path.
238
+ if ( isRelative ) {
239
+ continue ;
240
+ }
241
+ }
195
242
196
- // Trim off leading and trailing slashes. If all we had was
197
- // slashes, we're at the root of the Application.dataPath so return
198
- // "."
199
- relativePath = relativePath . Trim ( Path . DirectorySeparatorChar ) ;
200
- if ( string . IsNullOrEmpty ( relativePath ) ) {
201
- relativePath = "." ;
243
+ // Skip '.'
244
+ if ( dir == "." ) {
245
+ continue ;
246
+ }
247
+
248
+ // Erase the previous directory we read on '..'.
249
+ // Exception: we can start with '..'
250
+ // Exception: we can have multiple '..' in a row.
251
+ //
252
+ // Note: this ignores the actual file system and the funny
253
+ // results you see when there are symlinks.
254
+ if ( dir == ".." ) {
255
+ if ( lastWriteIndex == - 1 ) {
256
+ // Leading '..' => handle like a normal directory.
257
+ } else if ( dirs [ lastWriteIndex ] == ".." ) {
258
+ // Multiple ".." => handle like a normal directory.
259
+ } else {
260
+ // Usual case: delete the previous directory.
261
+ lastWriteIndex -- ;
262
+ continue ;
263
+ }
264
+ }
265
+
266
+ // Copy anything else to the next index.
267
+ ++ lastWriteIndex ;
268
+ dirs [ lastWriteIndex ] = dirs [ readIndex ] ;
269
+ }
270
+
271
+ if ( lastWriteIndex == - 1 || ( lastWriteIndex == 0 && dirs [ lastWriteIndex ] == "" ) ) {
272
+ // If we didn't keep anything, we have the empty path.
273
+ // For an absolute path that's / ; for a relative path it's .
274
+ if ( isRelative ) {
275
+ return "." ;
276
+ } else {
277
+ return "" + separator ;
278
+ }
279
+ } else {
280
+ // Otherwise print out the path with the proper separator.
281
+ return String . Join ( "" + separator , dirs , 0 , lastWriteIndex + 1 ) ;
202
282
}
203
- return relativePath ;
204
283
}
205
284
206
285
[ MenuItem ( "Edit/Project Settings/Fbx Export" , priority = 300 ) ]
0 commit comments