com.wallstop-studios.unity-helpers 2.0.0-rc68 → 2.0.0-rc70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Editor/AnimationCopier.cs +875 -93
- package/Editor/AnimationCreator.cs +840 -137
- package/Editor/AnimationEventEditor.cs +4 -4
- package/Editor/AnimatorControllerCopier.cs +3 -3
- package/Editor/Extensions/UnityExtensions.cs +26 -0
- package/Editor/Extensions/UnityExtensions.cs.meta +3 -0
- package/Editor/Extensions.meta +3 -0
- package/Editor/FitTextureSizeWindow.cs +371 -0
- package/Editor/PrefabChecker.cs +716 -0
- package/Editor/SpriteAtlasGenerator.cs +598 -0
- package/Editor/SpriteAtlasGenerator.cs.meta +3 -0
- package/Editor/SpriteCropper.cs +407 -0
- package/Editor/SpriteCropper.cs.meta +3 -0
- package/Editor/SpriteSettingsApplier.cs +756 -92
- package/Editor/TextureResizerWizard.cs +3 -3
- package/Editor/TextureSettingsApplier.cs +9 -9
- package/Editor/WShowIfPropertyDrawer.cs +2 -2
- package/Runtime/Core/Attributes/EnumDisplayNameAttribute.cs +15 -0
- package/Runtime/Core/Attributes/EnumDisplayNameAttribute.cs.meta +3 -0
- package/Runtime/Core/Attributes/ValidateAssignmentAttribute.cs +1 -1
- package/Runtime/Core/Extension/EnumExtensions.cs +176 -1
- package/Runtime/Core/Extension/UnityExtensions.cs +1 -1
- package/Runtime/Core/Helper/Partials/LogHelpers.cs +1 -1
- package/Runtime/Core/Helper/Partials/MathHelpers.cs +1 -1
- package/Runtime/Core/Helper/Partials/ObjectHelpers.cs +3 -4
- package/Runtime/Tags/Attribute.cs +205 -0
- package/Runtime/Tags/Attribute.cs.meta +3 -0
- package/Runtime/Tags/AttributeEffect.cs +276 -0
- package/Runtime/Tags/AttributeEffect.cs.meta +3 -0
- package/Runtime/Tags/AttributeModification.cs +51 -0
- package/Runtime/Tags/AttributeModification.cs.meta +3 -0
- package/Runtime/Tags/AttributeUtilities.cs +209 -0
- package/Runtime/Tags/AttributeUtilities.cs.meta +3 -0
- package/Runtime/Tags/AttributesComponent.cs +163 -0
- package/Runtime/Tags/AttributesComponent.cs.meta +3 -0
- package/Runtime/Tags/CosmeticEffectComponent.cs +50 -0
- package/Runtime/Tags/CosmeticEffectComponent.cs.meta +3 -0
- package/Runtime/Tags/CosmeticEffectData.cs +63 -0
- package/Runtime/Tags/CosmeticEffectData.cs.meta +3 -0
- package/Runtime/Tags/EffectHandle.cs +63 -0
- package/Runtime/Tags/EffectHandle.cs.meta +3 -0
- package/Runtime/Tags/EffectHandler.cs +380 -0
- package/Runtime/Tags/EffectHandler.cs.meta +3 -0
- package/Runtime/Tags/ModificationAction.cs +9 -0
- package/Runtime/Tags/ModificationAction.cs.meta +3 -0
- package/Runtime/Tags/ModifierDurationType.cs +13 -0
- package/Runtime/Tags/ModifierDurationType.cs.meta +3 -0
- package/Runtime/{Utils → Tags}/TagHandler.cs +42 -5
- package/Runtime/Tags.meta +3 -0
- package/Tests/Runtime/DataStructures/BalancedKDTreeTests.cs +1 -1
- package/Tests/Runtime/DataStructures/CyclicBufferTests.cs +1 -1
- package/Tests/Runtime/DataStructures/QuadTreeTests.cs +1 -1
- package/Tests/Runtime/DataStructures/SpatialTreeTests.cs +1 -1
- package/Tests/Runtime/DataStructures/UnbalancedKDTreeTests.cs +1 -1
- package/Tests/Runtime/Extensions/DictionaryExtensionTests.cs +1 -1
- package/Tests/Runtime/Extensions/EnumExtensionTests.cs +1 -1
- package/Tests/Runtime/Extensions/IListExtensionTests.cs +1 -1
- package/Tests/Runtime/Extensions/LoggingExtensionTests.cs +1 -1
- package/Tests/Runtime/Extensions/RandomExtensionTests.cs +1 -1
- package/Tests/Runtime/Extensions/StringExtensionTests.cs +1 -1
- package/Tests/Runtime/Helper/ObjectHelperTests.cs +1 -0
- package/Tests/Runtime/Helper/WallMathTests.cs +1 -1
- package/Tests/Runtime/Performance/KDTreePerformanceTests.cs +1 -1
- package/Tests/Runtime/Performance/QuadTreePerformanceTests.cs +1 -1
- package/Tests/Runtime/Performance/SpatialTreePerformanceTest.cs +1 -1
- package/Tests/Runtime/Performance/UnbalancedKDTreeTests.cs +1 -1
- package/Tests/Runtime/Random/RandomTestBase.cs +2 -2
- package/Tests/Runtime/Serialization/JsonSerializationTest.cs +1 -1
- package/package.json +1 -1
- package/Editor/FitTextureSizeWizard.cs +0 -147
- package/Editor/PrefabCheckWizard.cs +0 -167
- /package/Editor/{FitTextureSizeWizard.cs.meta → FitTextureSizeWindow.cs.meta} +0 -0
- /package/Editor/{PrefabCheckWizard.cs.meta → PrefabChecker.cs.meta} +0 -0
- /package/Runtime/{Utils → Tags}/TagHandler.cs.meta +0 -0
|
@@ -2,172 +2,954 @@
|
|
|
2
2
|
{
|
|
3
3
|
#if UNITY_EDITOR
|
|
4
4
|
using System;
|
|
5
|
+
using System.Collections.Generic;
|
|
5
6
|
using System.IO;
|
|
7
|
+
using System.Linq;
|
|
8
|
+
using System.Security.Cryptography;
|
|
6
9
|
using UnityEditor;
|
|
7
10
|
using UnityEngine;
|
|
8
|
-
using
|
|
9
|
-
using WallstopStudios.UnityHelpers.Core.Attributes;
|
|
10
|
-
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
11
|
+
using Core.Extension;
|
|
11
12
|
|
|
12
|
-
public sealed class
|
|
13
|
+
public sealed class AnimationCopierWindow : EditorWindow
|
|
13
14
|
{
|
|
14
|
-
private string
|
|
15
|
-
private string
|
|
15
|
+
private const string SourcePathPrefKey = "AnimationCopier_SourcePathRelative";
|
|
16
|
+
private const string DestPathPrefKey = "AnimationCopier_DestPathRelative";
|
|
17
|
+
private const string DefaultSourcePath = "Assets/Sprites";
|
|
18
|
+
private const string DefaultDestPath = "Assets/Animations";
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
private string _animationSourcePathRelative = "";
|
|
21
|
+
private string _animationDestinationPathRelative = "";
|
|
22
|
+
private string _fullSourcePath = "";
|
|
23
|
+
private string _fullDestinationPath = "";
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
private bool _analysisNeeded = true;
|
|
26
|
+
private bool _isAnalyzing;
|
|
27
|
+
private bool _isCopying;
|
|
28
|
+
private bool _isDeleting;
|
|
29
|
+
|
|
30
|
+
private readonly List<AnimationFileInfo> _sourceAnimations = new();
|
|
31
|
+
private readonly List<AnimationFileInfo> _newAnimations = new();
|
|
32
|
+
private readonly List<AnimationFileInfo> _changedAnimations = new();
|
|
33
|
+
private readonly List<AnimationFileInfo> _unchangedAnimations = new();
|
|
34
|
+
|
|
35
|
+
private enum AnimationStatus
|
|
36
|
+
{
|
|
37
|
+
Unknown,
|
|
38
|
+
New,
|
|
39
|
+
Changed,
|
|
40
|
+
Unchanged,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private enum CopyMode
|
|
44
|
+
{
|
|
45
|
+
All,
|
|
46
|
+
Changed,
|
|
47
|
+
New,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private class AnimationFileInfo
|
|
51
|
+
{
|
|
52
|
+
public string RelativePath { get; set; }
|
|
53
|
+
public string FullPath { get; set; }
|
|
54
|
+
public string FileName { get; set; }
|
|
55
|
+
public string RelativeDirectory { get; set; }
|
|
56
|
+
public string Hash { get; set; }
|
|
57
|
+
public AnimationStatus Status { get; set; } = AnimationStatus.Unknown;
|
|
58
|
+
public string DestinationRelativePath { get; set; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[MenuItem("Tools/Wallstop Studios/Unity Helpers/Animation Copier Window", priority = -2)]
|
|
62
|
+
public static void ShowWindow()
|
|
63
|
+
{
|
|
64
|
+
GetWindow<AnimationCopierWindow>("Animation Copier");
|
|
65
|
+
}
|
|
22
66
|
|
|
23
67
|
private void OnEnable()
|
|
24
68
|
{
|
|
25
|
-
|
|
69
|
+
LoadPaths();
|
|
70
|
+
ValidatePaths();
|
|
71
|
+
_analysisNeeded = true;
|
|
72
|
+
this.Log($"Animation Copier Window opened.");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private void OnGUI()
|
|
76
|
+
{
|
|
77
|
+
bool operationInProgress = _isAnalyzing || _isCopying || _isDeleting;
|
|
78
|
+
|
|
79
|
+
if (operationInProgress)
|
|
80
|
+
{
|
|
81
|
+
string status =
|
|
82
|
+
_isAnalyzing ? "Analyzing..."
|
|
83
|
+
: _isCopying ? "Copying..."
|
|
84
|
+
: "Deleting...";
|
|
85
|
+
EditorGUILayout.LabelField(status, EditorStyles.centeredGreyMiniLabel);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
EditorGUI.BeginDisabledGroup(operationInProgress);
|
|
89
|
+
|
|
90
|
+
DrawPathSection(
|
|
91
|
+
"Source Path",
|
|
92
|
+
ref _animationSourcePathRelative,
|
|
93
|
+
ref _fullSourcePath,
|
|
94
|
+
SourcePathPrefKey
|
|
95
|
+
);
|
|
96
|
+
EditorGUILayout.Separator();
|
|
97
|
+
DrawPathSection(
|
|
98
|
+
"Destination Path",
|
|
99
|
+
ref _animationDestinationPathRelative,
|
|
100
|
+
ref _fullDestinationPath,
|
|
101
|
+
DestPathPrefKey
|
|
102
|
+
);
|
|
103
|
+
EditorGUILayout.Separator();
|
|
104
|
+
|
|
105
|
+
DrawAnalysisSection();
|
|
106
|
+
EditorGUILayout.Separator();
|
|
107
|
+
DrawCopySection();
|
|
108
|
+
EditorGUILayout.Separator();
|
|
109
|
+
DrawCleanupSection();
|
|
110
|
+
|
|
111
|
+
EditorGUI.EndDisabledGroup();
|
|
112
|
+
|
|
113
|
+
if (!operationInProgress && _analysisNeeded && Event.current.type == EventType.Layout)
|
|
26
114
|
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
115
|
+
if (ArePathsValid())
|
|
116
|
+
{
|
|
117
|
+
AnalyzeAnimations();
|
|
118
|
+
}
|
|
119
|
+
else
|
|
30
120
|
{
|
|
31
|
-
|
|
121
|
+
ClearAnalysisResults();
|
|
32
122
|
}
|
|
123
|
+
_analysisNeeded = false;
|
|
124
|
+
Repaint();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private void DrawPathSection(
|
|
129
|
+
string label,
|
|
130
|
+
ref string relativePath,
|
|
131
|
+
ref string fullPath,
|
|
132
|
+
string prefKey
|
|
133
|
+
)
|
|
134
|
+
{
|
|
135
|
+
if (!prefKey.StartsWith("WallstopStudios.UnityHelpers.Editor"))
|
|
136
|
+
{
|
|
137
|
+
prefKey = "WallstopStudios.UnityHelpers.Editor" + prefKey;
|
|
33
138
|
}
|
|
139
|
+
EditorGUILayout.LabelField(label + ":", EditorStyles.boldLabel);
|
|
34
140
|
|
|
35
|
-
|
|
141
|
+
EditorGUI.BeginChangeCheck();
|
|
142
|
+
string newRelativePath = EditorGUILayout.TextField(relativePath ?? "");
|
|
143
|
+
if (EditorGUI.EndChangeCheck() && newRelativePath != relativePath)
|
|
36
144
|
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
145
|
+
if (string.IsNullOrWhiteSpace(newRelativePath))
|
|
146
|
+
{
|
|
147
|
+
relativePath = "";
|
|
148
|
+
fullPath = "";
|
|
149
|
+
EditorPrefs.SetString(prefKey, "");
|
|
150
|
+
ValidatePaths();
|
|
151
|
+
_analysisNeeded = true;
|
|
152
|
+
}
|
|
153
|
+
else
|
|
40
154
|
{
|
|
41
|
-
|
|
155
|
+
string tempFullPath = GetFullPathFromRelative(newRelativePath);
|
|
156
|
+
if (tempFullPath != null && Directory.Exists(tempFullPath))
|
|
157
|
+
{
|
|
158
|
+
relativePath = newRelativePath;
|
|
159
|
+
fullPath = tempFullPath;
|
|
160
|
+
EditorPrefs.SetString(prefKey, relativePath);
|
|
161
|
+
ValidatePaths();
|
|
162
|
+
_analysisNeeded = true;
|
|
163
|
+
}
|
|
164
|
+
else
|
|
165
|
+
{
|
|
166
|
+
this.LogWarn(
|
|
167
|
+
$"Manual path entry '{newRelativePath}' is invalid or not inside Assets. Please use the button."
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (GUILayout.Button("Browse..."))
|
|
174
|
+
{
|
|
175
|
+
string initialPath = Directory.Exists(fullPath) ? fullPath : Application.dataPath;
|
|
176
|
+
string selectedPath = EditorUtility.OpenFolderPanel(
|
|
177
|
+
$"Select {label}",
|
|
178
|
+
initialPath,
|
|
179
|
+
string.Empty
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (!string.IsNullOrWhiteSpace(selectedPath))
|
|
183
|
+
{
|
|
184
|
+
string newRelPath = GetRelativeAssetPath(selectedPath);
|
|
185
|
+
if (newRelPath != null)
|
|
186
|
+
{
|
|
187
|
+
relativePath = newRelPath;
|
|
188
|
+
fullPath = selectedPath.Replace(Path.DirectorySeparatorChar, '/');
|
|
189
|
+
EditorPrefs.SetString(prefKey, relativePath);
|
|
190
|
+
this.Log($"{label} set to: {relativePath}");
|
|
191
|
+
ValidatePaths();
|
|
192
|
+
_analysisNeeded = true;
|
|
193
|
+
Repaint();
|
|
194
|
+
}
|
|
195
|
+
else
|
|
196
|
+
{
|
|
197
|
+
EditorUtility.DisplayDialog(
|
|
198
|
+
"Invalid Path",
|
|
199
|
+
"The selected path must be inside the project's 'Assets' folder.",
|
|
200
|
+
"OK"
|
|
201
|
+
);
|
|
202
|
+
}
|
|
42
203
|
}
|
|
43
204
|
}
|
|
44
205
|
}
|
|
45
206
|
|
|
46
|
-
|
|
47
|
-
public static void CopyAnimations()
|
|
207
|
+
private void DrawAnalysisSection()
|
|
48
208
|
{
|
|
49
|
-
|
|
209
|
+
EditorGUILayout.LabelField("Analysis:", EditorStyles.boldLabel);
|
|
210
|
+
|
|
211
|
+
if (GUILayout.Button("Analyze Source & Destination"))
|
|
212
|
+
{
|
|
213
|
+
if (ArePathsValid())
|
|
214
|
+
{
|
|
215
|
+
AnalyzeAnimations();
|
|
216
|
+
}
|
|
217
|
+
else
|
|
218
|
+
{
|
|
219
|
+
EditorUtility.DisplayDialog(
|
|
220
|
+
"Error",
|
|
221
|
+
"Source or Destination path is not set or invalid.",
|
|
222
|
+
"OK"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
EditorGUILayout.Space();
|
|
228
|
+
|
|
229
|
+
EditorGUILayout.LabelField(
|
|
230
|
+
"Source Animations Found:",
|
|
231
|
+
_sourceAnimations.Count.ToString()
|
|
232
|
+
);
|
|
233
|
+
EditorGUILayout.LabelField("- New:", _newAnimations.Count.ToString());
|
|
234
|
+
EditorGUILayout.LabelField("- Changed:", _changedAnimations.Count.ToString());
|
|
235
|
+
EditorGUILayout.LabelField(
|
|
236
|
+
"- Unchanged (Duplicates):",
|
|
237
|
+
_unchangedAnimations.Count.ToString()
|
|
238
|
+
);
|
|
50
239
|
}
|
|
51
240
|
|
|
52
|
-
|
|
241
|
+
private void DrawCopySection()
|
|
53
242
|
{
|
|
54
|
-
|
|
243
|
+
EditorGUILayout.LabelField("Copy Actions:", EditorStyles.boldLabel);
|
|
244
|
+
|
|
245
|
+
bool canAnalyze = ArePathsValid();
|
|
246
|
+
bool analysisDone = !_analysisNeeded;
|
|
55
247
|
|
|
56
|
-
|
|
248
|
+
bool canCopyNew = canAnalyze && analysisDone && _newAnimations.Any();
|
|
249
|
+
bool canCopyChanged = canAnalyze && analysisDone && _changedAnimations.Any();
|
|
250
|
+
bool canCopyAll = canAnalyze && analysisDone && _sourceAnimations.Any();
|
|
251
|
+
|
|
252
|
+
EditorGUI.BeginDisabledGroup(!canCopyNew);
|
|
253
|
+
if (GUILayout.Button($"Copy New ({_newAnimations.Count})"))
|
|
57
254
|
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
255
|
+
if (
|
|
256
|
+
EditorUtility.DisplayDialog(
|
|
257
|
+
"Confirm Copy New",
|
|
258
|
+
$"Copy {_newAnimations.Count} new animations from '{_animationSourcePathRelative}' to '{_animationDestinationPathRelative}'?",
|
|
259
|
+
"Yes, Copy New",
|
|
260
|
+
"Cancel"
|
|
261
|
+
)
|
|
262
|
+
)
|
|
65
263
|
{
|
|
66
|
-
|
|
264
|
+
CopyAnimationsInternal(CopyMode.New);
|
|
67
265
|
}
|
|
266
|
+
}
|
|
267
|
+
EditorGUI.EndDisabledGroup();
|
|
68
268
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
269
|
+
EditorGUI.BeginDisabledGroup(!canCopyChanged);
|
|
270
|
+
if (GUILayout.Button($"Copy Changed ({_changedAnimations.Count})"))
|
|
271
|
+
{
|
|
272
|
+
if (
|
|
273
|
+
EditorUtility.DisplayDialog(
|
|
274
|
+
"Confirm Copy Changed",
|
|
275
|
+
$"Copy {_changedAnimations.Count} changed animations from '{_animationSourcePathRelative}' to '{_animationDestinationPathRelative}', overwriting existing files?",
|
|
276
|
+
"Yes, Copy Changed",
|
|
277
|
+
"Cancel"
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
{
|
|
281
|
+
CopyAnimationsInternal(CopyMode.Changed);
|
|
282
|
+
}
|
|
72
283
|
}
|
|
284
|
+
EditorGUI.EndDisabledGroup();
|
|
73
285
|
|
|
74
|
-
|
|
286
|
+
int totalToCopyAll =
|
|
287
|
+
_newAnimations.Count + _changedAnimations.Count + _unchangedAnimations.Count;
|
|
288
|
+
EditorGUI.BeginDisabledGroup(!canCopyAll);
|
|
289
|
+
if (GUILayout.Button($"Copy All ({totalToCopyAll})"))
|
|
75
290
|
{
|
|
76
|
-
string
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
291
|
+
string overwriteWarning =
|
|
292
|
+
_changedAnimations.Count + _unchangedAnimations.Count > 0
|
|
293
|
+
? $" This will overwrite {_changedAnimations.Count + _unchangedAnimations.Count} existing files."
|
|
294
|
+
: "";
|
|
295
|
+
if (
|
|
296
|
+
EditorUtility.DisplayDialog(
|
|
297
|
+
"Confirm Copy All",
|
|
298
|
+
$"Copy {totalToCopyAll} animations from '{_animationSourcePathRelative}' to '{_animationDestinationPathRelative}'?{overwriteWarning}",
|
|
299
|
+
"Yes, Copy All",
|
|
300
|
+
"Cancel"
|
|
301
|
+
)
|
|
302
|
+
)
|
|
83
303
|
{
|
|
84
|
-
|
|
304
|
+
CopyAnimationsInternal(CopyMode.All);
|
|
85
305
|
}
|
|
306
|
+
}
|
|
307
|
+
EditorGUI.EndDisabledGroup();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private void DrawCleanupSection()
|
|
311
|
+
{
|
|
312
|
+
EditorGUILayout.LabelField("Cleanup Actions:", EditorStyles.boldLabel);
|
|
86
313
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
314
|
+
bool canAnalyze = ArePathsValid();
|
|
315
|
+
bool analysisDone = !_analysisNeeded;
|
|
316
|
+
bool hasUnchanged = _unchangedAnimations.Any();
|
|
317
|
+
|
|
318
|
+
if (canAnalyze && analysisDone && hasUnchanged)
|
|
319
|
+
{
|
|
320
|
+
Color originalColor = GUI.color;
|
|
321
|
+
GUI.color = Color.red;
|
|
322
|
+
|
|
323
|
+
string buttonText =
|
|
324
|
+
$"Delete {_unchangedAnimations.Count} Unchanged Source Duplicates";
|
|
325
|
+
|
|
326
|
+
if (GUILayout.Button(buttonText))
|
|
327
|
+
{
|
|
328
|
+
DeleteUnchangedSourceAnimations();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
GUI.color = originalColor;
|
|
90
332
|
}
|
|
333
|
+
else
|
|
334
|
+
{
|
|
335
|
+
EditorGUI.BeginDisabledGroup(true);
|
|
336
|
+
GUILayout.Button("Delete Unchanged Source Duplicates (None found)");
|
|
337
|
+
EditorGUI.EndDisabledGroup();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
91
340
|
|
|
92
|
-
|
|
341
|
+
private void LoadPaths()
|
|
342
|
+
{
|
|
343
|
+
_animationSourcePathRelative = EditorPrefs.GetString(
|
|
344
|
+
SourcePathPrefKey,
|
|
345
|
+
DefaultSourcePath
|
|
346
|
+
);
|
|
347
|
+
_animationDestinationPathRelative = EditorPrefs.GetString(
|
|
348
|
+
DestPathPrefKey,
|
|
349
|
+
DefaultDestPath
|
|
350
|
+
);
|
|
93
351
|
}
|
|
94
352
|
|
|
95
|
-
private void
|
|
353
|
+
private void ValidatePaths()
|
|
96
354
|
{
|
|
97
|
-
|
|
355
|
+
_fullSourcePath = GetFullPathFromRelative(_animationSourcePathRelative);
|
|
356
|
+
_fullDestinationPath = GetFullPathFromRelative(_animationDestinationPathRelative);
|
|
357
|
+
|
|
358
|
+
if (_fullSourcePath == null || !Directory.Exists(_fullSourcePath))
|
|
359
|
+
{
|
|
360
|
+
this.LogWarn(
|
|
361
|
+
$"Source path '{_animationSourcePathRelative}' is invalid or outside the project. Please set a valid path within Assets."
|
|
362
|
+
);
|
|
363
|
+
_fullSourcePath = null;
|
|
364
|
+
_analysisNeeded = true;
|
|
365
|
+
ClearAnalysisResults();
|
|
366
|
+
}
|
|
367
|
+
if (_fullDestinationPath == null)
|
|
98
368
|
{
|
|
369
|
+
this.LogWarn(
|
|
370
|
+
$"Destination path '{_animationDestinationPathRelative}' is invalid or outside the project. Please set a valid path within Assets."
|
|
371
|
+
);
|
|
372
|
+
_analysisNeeded = true;
|
|
373
|
+
ClearAnalysisResults();
|
|
374
|
+
}
|
|
375
|
+
else
|
|
376
|
+
{
|
|
377
|
+
string parentDir = Path.GetDirectoryName(_fullDestinationPath);
|
|
378
|
+
if (!Directory.Exists(parentDir))
|
|
379
|
+
{
|
|
380
|
+
this.LogWarn(
|
|
381
|
+
$"The parent directory for the destination path '{_animationDestinationPathRelative}' does not exist ('{parentDir}'). Copy operations may fail to create folders."
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private bool ArePathsValid()
|
|
388
|
+
{
|
|
389
|
+
return !string.IsNullOrWhiteSpace(_animationSourcePathRelative)
|
|
390
|
+
&& !string.IsNullOrWhiteSpace(_animationDestinationPathRelative)
|
|
391
|
+
&& _fullSourcePath != null
|
|
392
|
+
&& _fullDestinationPath != null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private void AnalyzeAnimations()
|
|
396
|
+
{
|
|
397
|
+
if (!ArePathsValid())
|
|
398
|
+
{
|
|
399
|
+
this.LogError($"Cannot analyze: Paths are invalid.");
|
|
400
|
+
ClearAnalysisResults();
|
|
401
|
+
_analysisNeeded = false;
|
|
402
|
+
Repaint();
|
|
99
403
|
return;
|
|
100
404
|
}
|
|
101
405
|
|
|
102
|
-
if (
|
|
103
|
-
string.IsNullOrEmpty(animationSourcePath)
|
|
104
|
-
|| string.IsNullOrEmpty(animationDestinationPath)
|
|
105
|
-
)
|
|
406
|
+
if (_isAnalyzing || _isCopying || _isDeleting)
|
|
106
407
|
{
|
|
107
408
|
return;
|
|
108
409
|
}
|
|
109
410
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
)
|
|
411
|
+
this.Log($"Starting animation analysis...");
|
|
412
|
+
_isAnalyzing = true;
|
|
413
|
+
ClearAnalysisResults();
|
|
414
|
+
Repaint();
|
|
415
|
+
|
|
416
|
+
try
|
|
117
417
|
{
|
|
118
|
-
string
|
|
418
|
+
string[] sourceGuids = AssetDatabase.FindAssets(
|
|
419
|
+
"t:AnimationClip",
|
|
420
|
+
new[] { _animationSourcePathRelative }
|
|
421
|
+
);
|
|
422
|
+
_sourceAnimations.Clear();
|
|
423
|
+
|
|
424
|
+
float total = sourceGuids.Length * 2;
|
|
425
|
+
int current = 0;
|
|
119
426
|
|
|
120
|
-
|
|
121
|
-
|
|
427
|
+
EditorUtility.DisplayProgressBar(
|
|
428
|
+
"Analyzing Animations",
|
|
429
|
+
"Gathering source files...",
|
|
430
|
+
0f
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
foreach (string guid in sourceGuids)
|
|
122
434
|
{
|
|
123
|
-
|
|
124
|
-
|
|
435
|
+
current++;
|
|
436
|
+
string sourceRelPath = AssetDatabase.GUIDToAssetPath(guid);
|
|
437
|
+
if (
|
|
438
|
+
string.IsNullOrWhiteSpace(sourceRelPath)
|
|
439
|
+
|| !sourceRelPath.StartsWith(_animationSourcePathRelative)
|
|
440
|
+
)
|
|
441
|
+
{
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
string sourceFullPath = GetFullPathFromRelative(sourceRelPath);
|
|
446
|
+
if (sourceFullPath == null || !File.Exists(sourceFullPath))
|
|
447
|
+
{
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
string directoryName = Path.GetDirectoryName(sourceRelPath);
|
|
452
|
+
if (string.IsNullOrWhiteSpace(directoryName))
|
|
453
|
+
{
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
AnimationFileInfo fileInfo = new()
|
|
457
|
+
{
|
|
458
|
+
RelativePath = sourceRelPath,
|
|
459
|
+
FullPath = sourceFullPath,
|
|
460
|
+
FileName = Path.GetFileName(sourceRelPath),
|
|
461
|
+
RelativeDirectory = GetRelativeSubPath(
|
|
462
|
+
_animationSourcePathRelative,
|
|
463
|
+
directoryName.Replace(Path.DirectorySeparatorChar, '/')
|
|
464
|
+
),
|
|
465
|
+
Hash = CalculateFileHash(sourceFullPath),
|
|
466
|
+
};
|
|
467
|
+
fileInfo.DestinationRelativePath = Path.Combine(
|
|
468
|
+
_animationDestinationPathRelative,
|
|
469
|
+
fileInfo.RelativeDirectory,
|
|
470
|
+
fileInfo.FileName
|
|
471
|
+
)
|
|
472
|
+
.Replace(Path.DirectorySeparatorChar, '/');
|
|
473
|
+
_sourceAnimations.Add(fileInfo);
|
|
474
|
+
|
|
475
|
+
EditorUtility.DisplayProgressBar(
|
|
476
|
+
"Analyzing Animations",
|
|
477
|
+
$"Hashing: {fileInfo.FileName}",
|
|
478
|
+
current / total
|
|
125
479
|
);
|
|
126
|
-
continue;
|
|
127
480
|
}
|
|
128
481
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
int prefixIndex = relativePath.LastIndexOf(
|
|
132
|
-
prefix,
|
|
133
|
-
StringComparison.OrdinalIgnoreCase
|
|
482
|
+
this.Log(
|
|
483
|
+
$"Found {_sourceAnimations.Count} animations in source. Comparing with destination..."
|
|
134
484
|
);
|
|
135
|
-
|
|
485
|
+
|
|
486
|
+
for (int i = 0; i < _sourceAnimations.Count; i++)
|
|
136
487
|
{
|
|
137
|
-
|
|
138
|
-
|
|
488
|
+
AnimationFileInfo sourceInfo = _sourceAnimations[i];
|
|
489
|
+
current++;
|
|
490
|
+
EditorUtility.DisplayProgressBar(
|
|
491
|
+
"Analyzing Animations",
|
|
492
|
+
$"Comparing: {sourceInfo.FileName}",
|
|
493
|
+
current / total
|
|
139
494
|
);
|
|
140
|
-
|
|
495
|
+
|
|
496
|
+
string destRelPath = sourceInfo.DestinationRelativePath;
|
|
497
|
+
string destFullPath = GetFullPathFromRelative(destRelPath);
|
|
498
|
+
bool destExists = destFullPath != null && File.Exists(destFullPath);
|
|
499
|
+
|
|
500
|
+
if (!destExists)
|
|
501
|
+
{
|
|
502
|
+
sourceInfo.Status = AnimationStatus.New;
|
|
503
|
+
_newAnimations.Add(sourceInfo);
|
|
504
|
+
}
|
|
505
|
+
else
|
|
506
|
+
{
|
|
507
|
+
string destHash = CalculateFileHash(destFullPath);
|
|
508
|
+
if (string.IsNullOrEmpty(sourceInfo.Hash) || string.IsNullOrEmpty(destHash))
|
|
509
|
+
{
|
|
510
|
+
this.LogWarn(
|
|
511
|
+
$"Could not compare '{sourceInfo.FileName}' due to hashing error. Treating as 'Changed'."
|
|
512
|
+
);
|
|
513
|
+
sourceInfo.Status = AnimationStatus.Changed;
|
|
514
|
+
_changedAnimations.Add(sourceInfo);
|
|
515
|
+
}
|
|
516
|
+
else if (
|
|
517
|
+
sourceInfo.Hash.Equals(destHash, StringComparison.OrdinalIgnoreCase)
|
|
518
|
+
)
|
|
519
|
+
{
|
|
520
|
+
sourceInfo.Status = AnimationStatus.Unchanged;
|
|
521
|
+
_unchangedAnimations.Add(sourceInfo);
|
|
522
|
+
}
|
|
523
|
+
else
|
|
524
|
+
{
|
|
525
|
+
sourceInfo.Status = AnimationStatus.Changed;
|
|
526
|
+
_changedAnimations.Add(sourceInfo);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
141
529
|
}
|
|
142
530
|
|
|
143
|
-
|
|
144
|
-
|
|
531
|
+
this.Log(
|
|
532
|
+
$"Analysis complete: {_newAnimations.Count} New, {_changedAnimations.Count} Changed, {_unchangedAnimations.Count} Unchanged."
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
catch (Exception ex)
|
|
536
|
+
{
|
|
537
|
+
this.LogError($"Error during analysis: {ex.Message}\n{ex.StackTrace}");
|
|
538
|
+
EditorUtility.DisplayDialog(
|
|
539
|
+
"Analysis Error",
|
|
540
|
+
$"An error occurred during analysis: {ex.Message}",
|
|
541
|
+
"OK"
|
|
542
|
+
);
|
|
543
|
+
ClearAnalysisResults();
|
|
544
|
+
}
|
|
545
|
+
finally
|
|
546
|
+
{
|
|
547
|
+
_isAnalyzing = false;
|
|
548
|
+
_analysisNeeded = false;
|
|
549
|
+
EditorUtility.ClearProgressBar();
|
|
550
|
+
Repaint();
|
|
551
|
+
}
|
|
552
|
+
}
|
|
145
553
|
|
|
146
|
-
|
|
554
|
+
private void CopyAnimationsInternal(CopyMode mode)
|
|
555
|
+
{
|
|
556
|
+
if (!ArePathsValid() || _isAnalyzing || _isCopying || _isDeleting)
|
|
557
|
+
{
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
List<AnimationFileInfo> animationsToCopy = new();
|
|
562
|
+
switch (mode)
|
|
563
|
+
{
|
|
564
|
+
case CopyMode.All:
|
|
565
|
+
animationsToCopy.AddRange(_newAnimations);
|
|
566
|
+
animationsToCopy.AddRange(_changedAnimations);
|
|
567
|
+
animationsToCopy.AddRange(_unchangedAnimations);
|
|
568
|
+
break;
|
|
569
|
+
case CopyMode.Changed:
|
|
570
|
+
animationsToCopy.AddRange(_changedAnimations);
|
|
571
|
+
break;
|
|
572
|
+
case CopyMode.New:
|
|
573
|
+
animationsToCopy.AddRange(_newAnimations);
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (animationsToCopy.Count == 0)
|
|
578
|
+
{
|
|
579
|
+
this.Log($"No animations to copy for the selected mode.");
|
|
580
|
+
EditorUtility.DisplayDialog(
|
|
581
|
+
"Nothing to Copy",
|
|
582
|
+
"There are no animations matching the selected criteria.",
|
|
583
|
+
"OK"
|
|
584
|
+
);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
this.Log(
|
|
589
|
+
$"Starting copy operation (Mode: {mode}) for {animationsToCopy.Count} animations..."
|
|
590
|
+
);
|
|
591
|
+
_isCopying = true;
|
|
592
|
+
Repaint();
|
|
593
|
+
|
|
594
|
+
int successCount = 0;
|
|
595
|
+
int errorCount = 0;
|
|
596
|
+
AssetDatabase.StartAssetEditing();
|
|
597
|
+
|
|
598
|
+
try
|
|
599
|
+
{
|
|
600
|
+
for (int i = 0; i < animationsToCopy.Count; i++)
|
|
147
601
|
{
|
|
148
|
-
|
|
602
|
+
AnimationFileInfo animInfo = animationsToCopy[i];
|
|
603
|
+
float progress = (float)(i + 1) / animationsToCopy.Count;
|
|
604
|
+
bool userCancelled = EditorUtility.DisplayCancelableProgressBar(
|
|
605
|
+
$"Copying Animations ({mode})",
|
|
606
|
+
$"Copying: {animInfo.FileName} ({i + 1}/{animationsToCopy.Count})",
|
|
607
|
+
progress
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
if (userCancelled)
|
|
611
|
+
{
|
|
612
|
+
this.LogWarn($"Copy operation cancelled by user.");
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
string sourceAssetPath = animInfo.RelativePath;
|
|
617
|
+
string destinationAssetPath = animInfo.DestinationRelativePath;
|
|
618
|
+
string destDirectory = Path.GetDirectoryName(destinationAssetPath);
|
|
619
|
+
|
|
620
|
+
if (
|
|
621
|
+
!string.IsNullOrEmpty(destDirectory)
|
|
622
|
+
&& !AssetDatabase.IsValidFolder(destDirectory)
|
|
623
|
+
)
|
|
624
|
+
{
|
|
625
|
+
try
|
|
626
|
+
{
|
|
627
|
+
EnsureDirectoryExists(destDirectory);
|
|
628
|
+
}
|
|
629
|
+
catch (Exception ex)
|
|
630
|
+
{
|
|
631
|
+
this.LogError(
|
|
632
|
+
$"Failed to create destination directory '{destDirectory}' for animation '{animInfo.FileName}'. Error: {ex.Message}. Skipping."
|
|
633
|
+
);
|
|
634
|
+
errorCount++;
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
bool copySuccessful = AssetDatabase.CopyAsset(
|
|
640
|
+
sourceAssetPath,
|
|
641
|
+
destinationAssetPath
|
|
642
|
+
);
|
|
643
|
+
|
|
644
|
+
if (copySuccessful)
|
|
645
|
+
{
|
|
646
|
+
successCount++;
|
|
647
|
+
}
|
|
648
|
+
else
|
|
649
|
+
{
|
|
650
|
+
this.LogError(
|
|
651
|
+
$"Failed to copy animation from '{sourceAssetPath}' to '{destinationAssetPath}'."
|
|
652
|
+
);
|
|
653
|
+
errorCount++;
|
|
654
|
+
}
|
|
149
655
|
}
|
|
656
|
+
}
|
|
657
|
+
catch (Exception ex)
|
|
658
|
+
{
|
|
659
|
+
this.LogError(
|
|
660
|
+
$"An unexpected error occurred during the copy process: {ex.Message}\n{ex.StackTrace}"
|
|
661
|
+
);
|
|
662
|
+
errorCount = animationsToCopy.Count - successCount;
|
|
663
|
+
}
|
|
664
|
+
finally
|
|
665
|
+
{
|
|
666
|
+
AssetDatabase.StopAssetEditing();
|
|
667
|
+
AssetDatabase.SaveAssets();
|
|
668
|
+
AssetDatabase.Refresh();
|
|
669
|
+
EditorUtility.ClearProgressBar();
|
|
670
|
+
_isCopying = false;
|
|
671
|
+
this.Log(
|
|
672
|
+
$"Copy operation finished. Mode: {mode}. Success: {successCount}, Errors: {errorCount}."
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
EditorUtility.DisplayDialog(
|
|
676
|
+
"Copy Complete",
|
|
677
|
+
$"Copy operation finished.\nMode: {mode}\nSuccessfully copied: {successCount}\nErrors: {errorCount}\n\nSee console log for details.",
|
|
678
|
+
"OK"
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
_analysisNeeded = true;
|
|
682
|
+
Repaint();
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
private void DeleteUnchangedSourceAnimations()
|
|
687
|
+
{
|
|
688
|
+
if (!ArePathsValid() || _isAnalyzing || _isCopying || _isDeleting)
|
|
689
|
+
{
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
List<AnimationFileInfo> animationsToDelete = _unchangedAnimations.ToList();
|
|
694
|
+
|
|
695
|
+
if (animationsToDelete.Count == 0)
|
|
696
|
+
{
|
|
697
|
+
this.Log($"No unchanged source animations to delete.");
|
|
698
|
+
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
this.Log(
|
|
703
|
+
$"Starting delete operation for {animationsToDelete.Count} unchanged source animations..."
|
|
704
|
+
);
|
|
705
|
+
_isDeleting = true;
|
|
706
|
+
Repaint();
|
|
150
707
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
708
|
+
int successCount = 0;
|
|
709
|
+
int errorCount = 0;
|
|
710
|
+
AssetDatabase.StartAssetEditing();
|
|
711
|
+
|
|
712
|
+
try
|
|
713
|
+
{
|
|
714
|
+
for (int i = 0; i < animationsToDelete.Count; i++)
|
|
155
715
|
{
|
|
156
|
-
|
|
157
|
-
|
|
716
|
+
AnimationFileInfo animInfo = animationsToDelete[i];
|
|
717
|
+
float progress = (float)(i + 1) / animationsToDelete.Count;
|
|
718
|
+
bool userCancelled = EditorUtility.DisplayCancelableProgressBar(
|
|
719
|
+
"Deleting Source Duplicates",
|
|
720
|
+
$"Deleting: {animInfo.FileName} ({i + 1}/{animationsToDelete.Count})",
|
|
721
|
+
progress
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
if (userCancelled)
|
|
158
725
|
{
|
|
159
|
-
this.
|
|
726
|
+
this.LogWarn($"Delete operation cancelled by user.");
|
|
727
|
+
break;
|
|
160
728
|
}
|
|
161
729
|
|
|
162
|
-
|
|
730
|
+
string sourceAssetPath = animInfo.RelativePath;
|
|
731
|
+
|
|
732
|
+
bool deleteSuccessful = AssetDatabase.DeleteAsset(sourceAssetPath);
|
|
733
|
+
|
|
734
|
+
if (deleteSuccessful)
|
|
735
|
+
{
|
|
736
|
+
successCount++;
|
|
737
|
+
}
|
|
738
|
+
else
|
|
739
|
+
{
|
|
740
|
+
this.LogError(
|
|
741
|
+
$"Failed to delete source duplicate: '{sourceAssetPath}'. It might have been moved or deleted already."
|
|
742
|
+
);
|
|
743
|
+
errorCount++;
|
|
744
|
+
}
|
|
163
745
|
}
|
|
164
|
-
|
|
746
|
+
}
|
|
747
|
+
catch (Exception ex)
|
|
748
|
+
{
|
|
749
|
+
this.LogError(
|
|
750
|
+
$"An unexpected error occurred during the delete process: {ex.Message}\n{ex.StackTrace}"
|
|
751
|
+
);
|
|
752
|
+
errorCount = animationsToDelete.Count - successCount;
|
|
753
|
+
}
|
|
754
|
+
finally
|
|
755
|
+
{
|
|
756
|
+
AssetDatabase.StopAssetEditing();
|
|
757
|
+
|
|
758
|
+
AssetDatabase.Refresh();
|
|
759
|
+
EditorUtility.ClearProgressBar();
|
|
760
|
+
_isDeleting = false;
|
|
761
|
+
this.Log(
|
|
762
|
+
$"Delete operation finished. Successfully deleted: {successCount}, Errors: {errorCount}."
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
_analysisNeeded = true;
|
|
766
|
+
Repaint();
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
private void ClearAnalysisResults()
|
|
771
|
+
{
|
|
772
|
+
_sourceAnimations.Clear();
|
|
773
|
+
_newAnimations.Clear();
|
|
774
|
+
_changedAnimations.Clear();
|
|
775
|
+
_unchangedAnimations.Clear();
|
|
776
|
+
|
|
777
|
+
Repaint();
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
private static string GetRelativeAssetPath(string fullPath)
|
|
781
|
+
{
|
|
782
|
+
if (string.IsNullOrWhiteSpace(fullPath))
|
|
783
|
+
{
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
fullPath = fullPath.Replace(Path.DirectorySeparatorChar, '/');
|
|
788
|
+
if (
|
|
789
|
+
fullPath.EndsWith("/Assets", StringComparison.OrdinalIgnoreCase)
|
|
790
|
+
&& Path.GetFileName(fullPath).Equals("Assets", StringComparison.OrdinalIgnoreCase)
|
|
791
|
+
)
|
|
792
|
+
{
|
|
793
|
+
return "Assets";
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
string assetsPath = Application.dataPath.Replace(Path.DirectorySeparatorChar, '/');
|
|
797
|
+
if (fullPath.StartsWith(assetsPath, StringComparison.OrdinalIgnoreCase))
|
|
798
|
+
{
|
|
799
|
+
if (fullPath.Length == assetsPath.Length)
|
|
800
|
+
{
|
|
801
|
+
return "Assets";
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
int startIndex = assetsPath.Length;
|
|
805
|
+
if (fullPath.Length > startIndex && fullPath[startIndex] == '/')
|
|
806
|
+
{
|
|
807
|
+
startIndex++;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return "Assets/" + fullPath.Substring(startIndex);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
int assetIndex = fullPath.IndexOf("/Assets/", StringComparison.OrdinalIgnoreCase);
|
|
814
|
+
if (assetIndex >= 0)
|
|
815
|
+
{
|
|
816
|
+
return fullPath.Substring(assetIndex + 1);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
private static string GetFullPathFromRelative(string relativePath)
|
|
823
|
+
{
|
|
824
|
+
if (string.IsNullOrWhiteSpace(relativePath))
|
|
825
|
+
{
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (relativePath.Equals("Assets", StringComparison.OrdinalIgnoreCase))
|
|
830
|
+
{
|
|
831
|
+
return Application.dataPath.Replace(Path.DirectorySeparatorChar, '/');
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (relativePath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase))
|
|
835
|
+
{
|
|
836
|
+
string projectRoot = Application.dataPath.Substring(
|
|
837
|
+
0,
|
|
838
|
+
Application.dataPath.Length - "Assets".Length
|
|
839
|
+
);
|
|
840
|
+
return (projectRoot + relativePath).Replace(Path.DirectorySeparatorChar, '/');
|
|
841
|
+
}
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
private string GetRelativeSubPath(string basePath, string fullPath)
|
|
846
|
+
{
|
|
847
|
+
string normalizedBasePath = basePath.TrimEnd('/') + "/";
|
|
848
|
+
string normalizedFullPath = fullPath.TrimEnd('/') + "/";
|
|
849
|
+
|
|
850
|
+
if (
|
|
851
|
+
normalizedFullPath.StartsWith(
|
|
852
|
+
normalizedBasePath,
|
|
853
|
+
StringComparison.OrdinalIgnoreCase
|
|
854
|
+
)
|
|
855
|
+
)
|
|
856
|
+
{
|
|
857
|
+
string subPath = normalizedFullPath
|
|
858
|
+
.Substring(normalizedBasePath.Length)
|
|
859
|
+
.TrimEnd('/');
|
|
860
|
+
return subPath;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
this.LogWarn(
|
|
864
|
+
$"Path '{fullPath}' did not start with expected base '{basePath}'. Could not determine relative sub-path."
|
|
865
|
+
);
|
|
866
|
+
return string.Empty;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
private static string CalculateFileHash(string filePath)
|
|
870
|
+
{
|
|
871
|
+
try
|
|
872
|
+
{
|
|
873
|
+
using (MD5 md5 = MD5.Create())
|
|
874
|
+
using (FileStream stream = File.OpenRead(filePath))
|
|
875
|
+
{
|
|
876
|
+
byte[] hashBytes = md5.ComputeHash(stream);
|
|
877
|
+
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
catch (IOException ioEx)
|
|
881
|
+
{
|
|
882
|
+
Debug.LogError(
|
|
883
|
+
$"[AnimationCopierWindow] IO Error calculating hash for {filePath}: {ioEx.Message}"
|
|
884
|
+
);
|
|
885
|
+
return string.Empty;
|
|
886
|
+
}
|
|
887
|
+
catch (Exception ex)
|
|
888
|
+
{
|
|
889
|
+
Debug.LogError(
|
|
890
|
+
$"[AnimationCopierWindow] Error calculating hash for {filePath}: {ex.Message}"
|
|
891
|
+
);
|
|
892
|
+
return string.Empty;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
private void EnsureDirectoryExists(string relativeDirectoryPath)
|
|
897
|
+
{
|
|
898
|
+
if (string.IsNullOrWhiteSpace(relativeDirectoryPath))
|
|
899
|
+
{
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (!relativeDirectoryPath.StartsWith("Assets/"))
|
|
903
|
+
{
|
|
904
|
+
if (relativeDirectoryPath.Equals("Assets", StringComparison.OrdinalIgnoreCase))
|
|
165
905
|
{
|
|
166
|
-
|
|
906
|
+
return;
|
|
167
907
|
}
|
|
908
|
+
|
|
909
|
+
this.LogError(
|
|
910
|
+
$"Attempted to create directory outside of Assets: '{relativeDirectoryPath}'"
|
|
911
|
+
);
|
|
912
|
+
throw new ArgumentException(
|
|
913
|
+
"Cannot create directories outside the Assets folder using AssetDatabase.",
|
|
914
|
+
nameof(relativeDirectoryPath)
|
|
915
|
+
);
|
|
168
916
|
}
|
|
169
917
|
|
|
170
|
-
|
|
918
|
+
if (AssetDatabase.IsValidFolder(relativeDirectoryPath))
|
|
919
|
+
{
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
string parentPath = Path.GetDirectoryName(relativeDirectoryPath)
|
|
924
|
+
?.Replace(Path.DirectorySeparatorChar, '/');
|
|
925
|
+
|
|
926
|
+
if (
|
|
927
|
+
string.IsNullOrEmpty(parentPath)
|
|
928
|
+
|| parentPath.Equals("Assets", StringComparison.OrdinalIgnoreCase)
|
|
929
|
+
)
|
|
930
|
+
{
|
|
931
|
+
string folderNameToCreate = Path.GetFileName(relativeDirectoryPath);
|
|
932
|
+
if (
|
|
933
|
+
!string.IsNullOrEmpty(folderNameToCreate)
|
|
934
|
+
&& !AssetDatabase.IsValidFolder(relativeDirectoryPath)
|
|
935
|
+
)
|
|
936
|
+
{
|
|
937
|
+
AssetDatabase.CreateFolder("Assets", folderNameToCreate);
|
|
938
|
+
}
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
EnsureDirectoryExists(parentPath);
|
|
943
|
+
|
|
944
|
+
string currentFolderName = Path.GetFileName(relativeDirectoryPath);
|
|
945
|
+
if (
|
|
946
|
+
!string.IsNullOrEmpty(currentFolderName)
|
|
947
|
+
&& !AssetDatabase.IsValidFolder(relativeDirectoryPath)
|
|
948
|
+
)
|
|
949
|
+
{
|
|
950
|
+
AssetDatabase.CreateFolder(parentPath, currentFolderName);
|
|
951
|
+
this.Log($"Created folder: {relativeDirectoryPath}");
|
|
952
|
+
}
|
|
171
953
|
}
|
|
172
954
|
}
|
|
173
955
|
#endif
|