1
+ using UnityEngine ;
2
+ using UnityEditor ;
3
+ using UnityEngine . TestTools ;
4
+ using NUnit . Framework ;
5
+ using System . IO ;
6
+ using System . Collections . Generic ;
7
+ using FbxSdk ;
8
+
9
+ namespace FbxExporters . UnitTests
10
+ {
11
+ /// <summary>
12
+ /// Tests the default selection export behavior.
13
+ /// Tests that the right GameObjects are exported and
14
+ /// that they have the expected transforms.
15
+ /// </summary>
16
+ public class DefaultSelectionTest
17
+ {
18
+ private string _filePath ;
19
+ protected string filePath { get { return string . IsNullOrEmpty ( _filePath ) ? Application . dataPath : _filePath ; } set { _filePath = value ; } }
20
+
21
+ private string _fileNamePrefix ;
22
+ protected string fileNamePrefix { get { return string . IsNullOrEmpty ( _fileNamePrefix ) ? "_safe_to_delete__" : _fileNamePrefix ; }
23
+ set { _fileNamePrefix = value ; } }
24
+
25
+ private string _fileNameExt ;
26
+ protected string fileNameExt { get { return string . IsNullOrEmpty ( _fileNameExt ) ? ".fbx" : _fileNameExt ; } set { _fileNameExt = value ; } }
27
+
28
+ private string MakeFileName ( string baseName = null , string prefixName = null , string extName = null )
29
+ {
30
+ if ( baseName == null )
31
+ baseName = Path . GetRandomFileName ( ) ;
32
+
33
+ if ( prefixName == null )
34
+ prefixName = this . fileNamePrefix ;
35
+
36
+ if ( extName == null )
37
+ extName = this . fileNameExt ;
38
+
39
+ return prefixName + baseName + extName ;
40
+ }
41
+
42
+ protected string GetRandomFileNamePath ( string pathName = null , string prefixName = null , string extName = null )
43
+ {
44
+ string temp ;
45
+
46
+ if ( pathName == null )
47
+ pathName = this . filePath ;
48
+
49
+ if ( prefixName == null )
50
+ prefixName = this . fileNamePrefix ;
51
+
52
+ if ( extName == null )
53
+ extName = this . fileNameExt ;
54
+
55
+ // repeat until you find a file that does not already exist
56
+ do {
57
+ temp = Path . Combine ( pathName , MakeFileName ( prefixName : prefixName , extName : extName ) ) ;
58
+
59
+ } while ( File . Exists ( temp ) ) ;
60
+
61
+ return temp ;
62
+ }
63
+
64
+ protected GameObject m_root ;
65
+
66
+ [ TearDown ]
67
+ public void Term ( )
68
+ {
69
+ foreach ( string file in Directory . GetFiles ( this . filePath , MakeFileName ( "*" ) ) ) {
70
+ File . Delete ( file ) ;
71
+ }
72
+ if ( m_root ) {
73
+ UnityEngine . Object . DestroyImmediate ( m_root ) ;
74
+ }
75
+ }
76
+
77
+ [ Test ]
78
+ public void TestDefaultSelection ( )
79
+ {
80
+ // Default selection behavior:
81
+ // - Export descendants
82
+ // - Don't export siblings
83
+ // - Don't export parents
84
+ // - If both a parent and descendant are selected,
85
+ // then result will be the same as if just the parent
86
+ // were selected
87
+ //
88
+ // Default transform export:
89
+ // - if there is only one root GameObject being exported
90
+ // then zero out the root transform, leave all descendants
91
+ // with local transform
92
+ // - if there are multiple root GameObjects, then export
93
+ // the global transform of each, and local transform
94
+ // of descendants
95
+
96
+ m_root = CreateHierarchy ( ) ;
97
+ Assert . IsNotNull ( m_root ) ;
98
+
99
+ // test Export Root
100
+ // Expected result: everything gets exported
101
+ // Expected transform: root is zeroed out, all other transforms unchanged
102
+ var exportedRoot = ExportSelection ( new Object [ ] { m_root } ) ;
103
+ CompareHierarchies ( m_root , exportedRoot , true , false ) ;
104
+ CompareGlobalTransform ( exportedRoot . transform ) ;
105
+
106
+ // test Export Parent1, Child1
107
+ // Expected result: Parent1, Child1, Child2
108
+ // Expected transform: Parent1 zeroed out, all other transforms unchanged
109
+ var parent1 = m_root . transform . Find ( "Parent1" ) ;
110
+ var child1 = parent1 . Find ( "Child1" ) ;
111
+ exportedRoot = ExportSelection ( new Object [ ] { parent1 . gameObject , child1 . gameObject } ) ;
112
+ CompareHierarchies ( parent1 . gameObject , exportedRoot , true , false ) ;
113
+ CompareGlobalTransform ( exportedRoot . transform ) ;
114
+
115
+ // test Export Child2
116
+ // Expected result: Child2
117
+ // Expected transform: Child2 zeroed out
118
+ var child2 = parent1 . Find ( "Child2" ) . gameObject ;
119
+ exportedRoot = ExportSelection ( new Object [ ] { child2 } ) ;
120
+ CompareHierarchies ( child2 , exportedRoot , true , false ) ;
121
+ CompareGlobalTransform ( exportedRoot . transform ) ;
122
+
123
+ // test Export Child2, Parent2
124
+ // Expected result: Parent2, Child3, Child2
125
+ // Expected transform: Child2 and Parent2 maintain global transform
126
+ var parent2 = m_root . transform . Find ( "Parent2" ) ;
127
+ exportedRoot = ExportSelection ( new Object [ ] { child2 , parent2 } ) ;
128
+
129
+ List < GameObject > children = new List < GameObject > ( ) ;
130
+ foreach ( Transform child in exportedRoot . transform ) {
131
+ children . Add ( child . gameObject ) ;
132
+ }
133
+ CompareHierarchies ( new GameObject [ ] { child2 , parent2 . gameObject } , children . ToArray ( ) ) ;
134
+ }
135
+
136
+ /// <summary>
137
+ /// Compares the global transform of expected
138
+ /// to the local transform of actual.
139
+ /// If expected is null, then compare to the identity matrix.
140
+ /// </summary>
141
+ /// <param name="actual">Actual.</param>
142
+ /// <param name="expected">Expected.</param>
143
+ private void CompareGlobalTransform ( Transform actual , Transform expected = null ) {
144
+ var actualMatrix = ConstructTRSMatrix ( actual ) ;
145
+ var expectedMatrix = expected == null ? new FbxAMatrix ( ) : ConstructTRSMatrix ( expected , false ) ;
146
+ Assert . AreEqual ( expectedMatrix , actualMatrix ) ;
147
+ }
148
+
149
+ /// <summary>
150
+ /// Constructs a TRS matrix (as an FbxAMatrix) from a tranform.
151
+ /// </summary>
152
+ /// <returns>The TRS matrix.</returns>
153
+ /// <param name="t">Transform.</param>
154
+ /// <param name="local">If set to <c>true</c> use local transform.</param>
155
+ private FbxAMatrix ConstructTRSMatrix ( Transform t , bool local = true )
156
+ {
157
+ var translation = local ? t . localPosition : t . position ;
158
+ var rotation = local ? t . localEulerAngles : t . eulerAngles ;
159
+ var scale = local ? t . localScale : t . lossyScale ;
160
+ return new FbxAMatrix (
161
+ new FbxVector4 ( translation . x , translation . y , translation . z ) ,
162
+ new FbxVector4 ( rotation . x , rotation . y , rotation . z ) ,
163
+ new FbxVector4 ( scale . x , scale . y , scale . z )
164
+ ) ;
165
+ }
166
+
167
+ private GameObject CreateHierarchy ( )
168
+ {
169
+ // Create the following hierarchy:
170
+ // Root
171
+ // -> Parent1
172
+ // ----> Child1
173
+ // ----> Child2
174
+ // -> Parent2
175
+ // ----> Child3
176
+
177
+ var root = CreateGameObject ( "Root" ) ;
178
+ SetTransform ( root . transform ,
179
+ new Vector3 ( 3 , 4 , - 6 ) ,
180
+ new Vector3 ( 45 , 10 , 34 ) ,
181
+ new Vector3 ( 2 , 1 , 3 ) ) ;
182
+
183
+ var parent1 = CreateGameObject ( "Parent1" , root . transform ) ;
184
+ SetTransform ( parent1 . transform ,
185
+ new Vector3 ( 53 , 0 , - 1 ) ,
186
+ new Vector3 ( 0 , 5 , 0 ) ,
187
+ new Vector3 ( 1 , 1 , 1 ) ) ;
188
+
189
+ var parent2 = CreateGameObject ( "Parent2" , root . transform ) ;
190
+ SetTransform ( parent2 . transform ,
191
+ new Vector3 ( 0 , 0 , 0 ) ,
192
+ new Vector3 ( 90 , 1 , 3 ) ,
193
+ new Vector3 ( 1 , 0.3f , 0.5f ) ) ;
194
+
195
+ parent1 . transform . SetAsFirstSibling ( ) ;
196
+
197
+ CreateGameObject ( "Child1" , parent1 . transform ) ;
198
+ CreateGameObject ( "Child2" , parent1 . transform ) ;
199
+ CreateGameObject ( "Child3" , parent2 . transform ) ;
200
+
201
+ return root ;
202
+ }
203
+
204
+ private void SetTransform ( Transform t , Vector3 pos , Vector3 rot , Vector3 scale ) {
205
+ t . localPosition = pos ;
206
+ t . localEulerAngles = rot ;
207
+ t . localScale = scale ;
208
+ }
209
+
210
+ private GameObject CreateGameObject ( string name , Transform parent = null )
211
+ {
212
+ var go = new GameObject ( name ) ;
213
+ go . transform . SetParent ( parent ) ;
214
+ return go ;
215
+ }
216
+
217
+ private void CompareHierarchies (
218
+ GameObject expectedHierarchy , GameObject actualHierarchy ,
219
+ bool ignoreName = false , bool compareTransform = true )
220
+ {
221
+ if ( ! ignoreName ) {
222
+ Assert . AreEqual ( expectedHierarchy . name , actualHierarchy . name ) ;
223
+ }
224
+
225
+ var expectedTransform = expectedHierarchy . transform ;
226
+ var actualTransform = actualHierarchy . transform ;
227
+
228
+ if ( compareTransform ) {
229
+ Assert . AreEqual ( expectedTransform , actualTransform ) ;
230
+ }
231
+
232
+ Assert . AreEqual ( expectedTransform . childCount , actualTransform . childCount ) ;
233
+
234
+ foreach ( Transform expectedChild in expectedTransform ) {
235
+ var actualChild = actualTransform . Find ( expectedChild . name ) ;
236
+ Assert . IsNotNull ( actualChild ) ;
237
+ CompareHierarchies ( expectedChild . gameObject , actualChild . gameObject ) ;
238
+ }
239
+ }
240
+
241
+ private void CompareHierarchies ( GameObject [ ] expectedHierarchy , GameObject [ ] actualHierarchy )
242
+ {
243
+ Assert . AreEqual ( expectedHierarchy . Length , actualHierarchy . Length ) ;
244
+
245
+ System . Array . Sort ( expectedHierarchy , delegate ( GameObject x , GameObject y ) {
246
+ return x . name . CompareTo ( y . name ) ;
247
+ } ) ;
248
+ System . Array . Sort ( actualHierarchy , delegate ( GameObject x , GameObject y ) {
249
+ return x . name . CompareTo ( y . name ) ;
250
+ } ) ;
251
+
252
+ for ( int i = 0 ; i < expectedHierarchy . Length ; i ++ ) {
253
+ CompareHierarchies ( expectedHierarchy [ i ] , actualHierarchy [ i ] , false , false ) ;
254
+ // if we are Comparing lists of hierarchies, that means that the transforms
255
+ // should be the global transform of expected, as there is no zeroed out root
256
+ CompareGlobalTransform ( actualHierarchy [ i ] . transform , expectedHierarchy [ i ] . transform ) ;
257
+ }
258
+ }
259
+
260
+ private GameObject ExportSelection ( Object [ ] selected )
261
+ {
262
+ // export selected to a file, then return the root
263
+ var filename = GetRandomFileNamePath ( ) ;
264
+
265
+ Debug . unityLogger . logEnabled = false ;
266
+ var fbxFileName = FbxExporters . Editor . ModelExporter . ExportObjects ( filename , selected ) as string ;
267
+ Debug . unityLogger . logEnabled = true ;
268
+
269
+ Assert . IsNotNull ( fbxFileName ) ;
270
+
271
+ // make filepath relative to project folder
272
+ if ( fbxFileName . StartsWith ( Application . dataPath , System . StringComparison . CurrentCulture ) )
273
+ {
274
+ fbxFileName = "Assets" + fbxFileName . Substring ( Application . dataPath . Length ) ;
275
+ }
276
+ // refresh the assetdata base so that we can query for the model
277
+ AssetDatabase . Refresh ( ) ;
278
+
279
+ Object unityMainAsset = AssetDatabase . LoadMainAssetAtPath ( fbxFileName ) ;
280
+ var fbxRoot = unityMainAsset as GameObject ;
281
+
282
+ Assert . IsNotNull ( fbxRoot ) ;
283
+ return fbxRoot ;
284
+ }
285
+ }
286
+ }
0 commit comments