Skip to content

Commit 2789a63

Browse files
author
Benoit Hudson
committed
UNI-22543: better use of slashes across platforms
Also fix the relative paths test (by fixing the function), which didn't work.
1 parent 3652a59 commit 2789a63

File tree

2 files changed

+272
-169
lines changed

2 files changed

+272
-169
lines changed

Assets/FbxExporters/Editor/FbxExportSettings.cs

Lines changed: 129 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ public override void OnInspectorGUI() {
2828
"In Maya some symbols such as spaces and accents get replaced when importing an FBX " +
2929
"(e.g. \"foo bar\" becomes \"fooFBXASC032bar\"). " +
3030
"On export, convert the names of GameObjects so they are Maya compatible." +
31-
(exportSettings.mayaCompatibleNames ? "" :
31+
(exportSettings.mayaCompatibleNames ? "" :
3232
"\n\nWARNING: Disabling this feature may result in lost material connections," +
3333
" and unexpected character replacements in Maya.")
3434
),
3535
exportSettings.mayaCompatibleNames);
36-
36+
3737
exportSettings.centerObjects = EditorGUILayout.Toggle (
3838
new GUIContent("Center Objects:",
3939
"Export objects centered around the union of the bounding box of selected objects"),
@@ -108,99 +108,178 @@ public class ExportSettings : ScriptableSingleton<ExportSettings>
108108

109109
/// <summary>
110110
/// 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.
112113
/// </summary>
113114
public static string GetRelativeSavePath() {
114115
var relativePath = instance.convertToModelSavePath;
115116
if (string.IsNullOrEmpty(relativePath)) {
116117
relativePath = kDefaultSavePath;
117118
}
118-
return NormalizeRelativePath(relativePath);
119+
return NormalizePath(relativePath, isRelative: true);
119120
}
120121

121122
/// <summary>
122123
/// 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.
124125
/// </summary>
125126
public static string GetAbsoluteSavePath() {
126127
var relativePath = GetRelativeSavePath();
127128
var absolutePath = Path.Combine(Application.dataPath, relativePath);
128-
return Path.GetFullPath(absolutePath);
129+
return NormalizePath(absolutePath, isRelative: false,
130+
separator: Path.DirectorySeparatorChar);
129131
}
130132

131133
/// <summary>
132134
/// Set the path where Convert To Model will save the new fbx and prefab.
133135
/// This is interpreted as being relative to the Application.dataPath
134136
/// </summary>
135137
public static void SetRelativeSavePath(string newPath) {
136-
instance.convertToModelSavePath = newPath
137-
.Replace('\\', '/')
138-
.TrimEnd(Path.DirectorySeparatorChar);
138+
instance.convertToModelSavePath = NormalizePath(newPath, isRelative: true);
139139
}
140140

141141
/// <summary>
142142
/// Convert an absolute path into a relative path like what you would
143143
/// get from GetRelativeSavePath.
144144
///
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.
148146
/// </summary>
149147
public static string ConvertToAssetRelativePath(string fullPathInAssets)
150148
{
151149
return GetRelativePath(Application.dataPath, fullPathInAssets);
152150
}
153151

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+
{
155158
// 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+
}
172178

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+
}
175188

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];
179195
}
180196

181-
return NormalizeRelativePath(relativePath);
197+
return string.Join("" + separator, relDirs);
182198
}
183199

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 = '/')
185212
{
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;
189220
}
190221

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+
}
195242

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);
202282
}
203-
return relativePath;
204283
}
205284

206285
[MenuItem("Edit/Project Settings/Fbx Export", priority = 300)]

0 commit comments

Comments
 (0)