public int Main(KeyData keyData, string testName, string path) { if (!keyData.targetObject) { keyData.targetObject = CreateTargetObject(testName, keyData.componentType); } Animation animOrig = keyData.targetObject.AddComponent(typeof(Animation)) as Animation; AnimationClip animClipOriginal = new AnimationClip(); var animCurvesOriginal = new AnimationCurve[keyData.NumProperties]; animClipOriginal.legacy = true; animClipOriginal.name = "anim_" + testName; for (int id = 0; id < keyData.NumProperties; id++) { // initialize keys Keyframe [] keys = new Keyframe [keyData.NumKeys]; for (int idx = 0; idx < keyData.NumKeys; idx++) { keys [idx].time = keyData.keyTimes [idx]; keys [idx].value = keyData.GetKeyValues(id) [idx]; } animCurvesOriginal[id] = new AnimationCurve(keys); animClipOriginal.SetCurve("", keyData.componentType, keyData.GetPropertyName(id), animCurvesOriginal[id]); } animOrig.AddClip(animClipOriginal, animClipOriginal.name); animOrig.clip = animClipOriginal; // NOTE: when we first cached the curves the tangents wheren't set. foreach (EditorCurveBinding curveBinding in AnimationUtility.GetCurveBindings(animOrig.clip)) { int id = keyData.GetIndexOf(curveBinding.propertyName); if (id == -1) { continue; } animCurvesOriginal[id] = AnimationUtility.GetEditorCurve(animOrig.clip, curveBinding); } // TODO: add extra parent so that we can test export/import of transforms var goRoot = new GameObject(); goRoot.name = "Root_" + testName; keyData.targetObject.transform.parent = goRoot.transform; //export the object var exportedFilePath = ModelExporter.ExportObject(path, goRoot, exportOptions); Assert.That(exportedFilePath, Is.EqualTo(path)); // TODO: Uni-34492 change importer settings of (newly exported model) // so that it's not resampled and it is legacy animation AnimTester.ConfigureImportSettings(path, keyData.importSettings); // create a scene GO so we can compare. #if DEBUG_UNITTEST GameObject prefabGO = AssetDatabase.LoadMainAssetAtPath(path) as GameObject; GameObject sceneGO = Object.Instantiate(prefabGO, keyData.targetObject.transform.localPosition, keyData.targetObject.transform.localRotation); sceneGO.name = "Imported_" + testName; #endif //acquire imported object from exported file AnimationClip animClipImported = GetClipFromFbx(path); ClipPropertyTest(animClipOriginal, animClipImported); int result = 0; foreach (EditorCurveBinding curveBinding in AnimationUtility.GetCurveBindings(animClipImported)) { AnimationCurve animCurveImported = AnimationUtility.GetEditorCurve(animClipImported, curveBinding); Assert.That(animCurveImported, Is.Not.Null); string propertyBinding = curveBinding.propertyName; int id = keyData.GetIndexOf(propertyBinding); bool hasQuatBinding = MapEulerToQuaternionPropertyName.TryGetValue(propertyBinding, out propertyBinding); bool isRotation = AnimationTestDataClass.m_rotationEulerNames.Contains(curveBinding.propertyName) || AnimationTestDataClass.m_rotationQuaternionNames.Contains(curveBinding.propertyName); if (id == -1) { id = keyData.GetIndexOf(propertyBinding); } #if DEBUG_UNITTEST Debug.Log(string.Format("propertyBinding={0} mappedBinding={1} id={2}", curveBinding.propertyName, propertyBinding, id)); #endif if (id != -1) { if (keyData.compareOriginalKeys) { // NOTE: we cannot compare the keys that exported quaternion but are imported as euler. if (!hasQuatBinding) { // compare against original keydata KeysTest(keyData.keyTimes, keyData.GetKeyValues(id), animCurveImported, curveBinding.propertyName); // compare against original animCurve KeysTest(animCurvesOriginal[id], animCurveImported, curveBinding.propertyName, keyComparer); } else { // compare by sampled keyvalues against original keydata KeyValuesTest(keyData.keyTimes, keyData.GetAltKeyValues(id), animCurveImported, curveBinding.propertyName, isRotation); } } else { // compare by sampled keyvalues against original animCurve KeyValuesTest(animCurvesOriginal[id], animCurveImported, curveBinding.propertyName, isRotation); } result++; } } return(result); }
/// <summary> /// Return an FBX asset that corresponds to 'toConvert'. /// /// If 'toConvert' is the root of an FBX asset, return it. /// /// If it's an instance in a scene the points to the root of an FBX /// asset, return that asset. /// /// Otherwise, export according to the paths and options, and /// return the new asset. /// </summary> /// <param name="toConvert">GameObject for which we want an fbx asset</param> /// <param name="fbxDirectoryFullPath">Export will choose an /// appropriate filename in this directory. Ignored if fbxFullPath is /// set. Ignored if toConvert is an fbx asset or an instance of an /// fbx.</param> /// <param name="fbxDirectoryFullPath">Export will create this /// file. Overrides fbxDirectoryFullPath. Ignored if toConvert is an /// fbx asset or an instance of an fbx.</param> /// <returns>The root of a model prefab asset.</returns> internal static GameObject GetOrCreateFbxAsset(GameObject toConvert, string fbxDirectoryFullPath = null, string fbxFullPath = null, ConvertToPrefabSettingsSerialize exportOptions = null) { if (toConvert == null) { throw new System.ArgumentNullException("toConvert"); } var mainAsset = GetFbxAssetOrNull(toConvert); if (mainAsset) { return(mainAsset); } if (string.IsNullOrEmpty(fbxFullPath)) { // Generate a unique filename. if (string.IsNullOrEmpty(fbxDirectoryFullPath)) { fbxDirectoryFullPath = UnityEditor.Formats.Fbx.Exporter.ExportSettings.FbxAbsoluteSavePath; } else { fbxDirectoryFullPath = Path.GetFullPath(fbxDirectoryFullPath); } var fbxBasename = ModelExporter.ConvertToValidFilename(toConvert.name + ".fbx"); fbxFullPath = Path.Combine(fbxDirectoryFullPath, fbxBasename); if (File.Exists(fbxFullPath)) { fbxFullPath = IncrementFileName(fbxDirectoryFullPath, fbxFullPath); } } var projectRelativePath = ExportSettings.GetProjectRelativePath(fbxFullPath); // Make sure that the object names in the hierarchy are unique. // The import back in to Unity would do this automatically but // we prefer to control it so that the Maya artist can see the // same names as exist in Unity. EnforceUniqueNames(new GameObject[] { toConvert }); // Export to FBX. It refreshes the database. { var fbxActualPath = ModelExporter.ExportObject( fbxFullPath, toConvert, exportOptions != null ? exportOptions : new ConvertToPrefabSettingsSerialize() ); if (fbxActualPath != fbxFullPath) { throw new ConvertToLinkedPrefabException("Failed to convert " + toConvert.name); } } // Replace w Model asset. LoadMainAssetAtPath wants a path // relative to the project, not relative to the assets folder. var unityMainAsset = AssetDatabase.LoadMainAssetAtPath(projectRelativePath) as GameObject; if (!unityMainAsset) { throw new ConvertToLinkedPrefabException("Failed to convert " + toConvert.name); } // Copy the mesh/materials from the FBX UpdateFromSourceRecursive(toConvert, unityMainAsset); return(unityMainAsset); }
public static void PackAnimations(string path, GameObject meshObject) { List <AnimationClip> clips = new List <AnimationClip>(); List <ModelImporterClipAnimation> clipDatas = new List <ModelImporterClipAnimation>(); var info = new DirectoryInfo(path); var fileInfo = info.GetFiles(); foreach (var file in fileInfo) { if (file.Extension == ".fbx" || file.Extension == ".FBX") { string relativePath = path + "/" + file.Name; AnimationClip clip = AssetDatabase.LoadAssetAtPath <AnimationClip>(relativePath); //Debug.Log(relativePath); if (clip != null) { clips.Add(clip); } if (alwaysFalse) { ModelImporter importer = AssetDatabase.LoadAssetAtPath <ModelImporter>(relativePath); if (importer != null) { clipDatas.AddRange(importer.clipAnimations); } } } } Debug.Log(clips.Count); //---getting all curves/keyframes--- if (alwaysFalse) { AnimationClip collectiveClip = new AnimationClip(); Dictionary <string, Container> collectiveCurves = new Dictionary <string, Container>(); float timeOffset = 0f; float maxTime = 0f; for (int i = 0; i < clips.Count; i++) { var bindings = AnimationUtility.GetCurveBindings(clips[i]); Debug.Log("Clip[i] Count of bindings: " + bindings.Length); foreach (var binding in bindings) { Debug.Log(binding.propertyName + ": Path: " + binding.path); if (!collectiveCurves.ContainsKey(binding.propertyName)) { collectiveCurves.Add(binding.propertyName, new Container(binding.type, binding.path)); } var curveToWriteTo = collectiveCurves[binding.propertyName]; var curveToReadFrom = AnimationUtility.GetEditorCurve(clips[i], binding); //Debug.Log("Keys: " + curve.keys.Length); maxTime = Mathf.Max(maxTime, curveToReadFrom.GetLastKey().time); for (int frame = 0; frame < curveToReadFrom.length; frame++) { var keyFrame = curveToReadFrom.keys[frame]; keyFrame.time += timeOffset; curveToWriteTo.curve.AddKey(keyFrame); } //collectiveClip.SetCurve() } timeOffset += maxTime; } foreach (var kvp in collectiveCurves) { collectiveClip.SetCurve(kvp.Value.path, kvp.Value.typeOf, kvp.Key, kvp.Value.curve); } } //---instantiating go and adding animation component GameObject clone = Instantiate(meshObject); clone.name = meshObject.name; if (alwaysFalse) { var anim = clone.AddComponent <Animation>(); AnimationUtility.SetAnimationClips(anim, clips.ToArray()); } if (true) { var anim = clone.GetComponent <Animator>(); if (!anim) { anim = clone.AddComponent <Animator>(); } var controller = AnimatorController.CreateAnimatorControllerAtPath(path + "/temp.controller"); foreach (var clip in clips) { controller.AddMotion(clip); } anim.runtimeAnimatorController = controller; } if (alwaysFalse) { var fbxManager = FbxManager.Create(); var settings = fbxManager.GetIOSettings(); var exporter = FbxExporter.Create(fbxManager, "name"); //exporter. } if (true) { ModelExporter.ExportObject(path + "/combined.fbx", clone); } if (alwaysFalse) { using (FbxManager fbxManager = FbxManager.Create()) { // configure IO settings. var settings = FbxIOSettings.Create(fbxManager, Globals.IOSROOT); fbxManager.SetIOSettings(settings); // Export the scene using (FbxExporter exporter = FbxExporter.Create(fbxManager, "myExporter")) { // Initialize the exporter. bool status = exporter.Initialize("combindObjects", -1, fbxManager.GetIOSettings()); // Create a new scene to export FbxScene scene = FbxScene.Create(fbxManager, "myScene"); //FbxObject obj = FbxObject.Create(fbxManager, "combinedThings"); // Export the scene to the file. exporter.Export(scene); } } } //AssetDatabase.CreateAsset(collectiveClip, path + "/combined.anim"); //AssetDatabase.SaveAssets(); //Animation newAnimation = GetComponent<Animation>(); //Debug.Log(newAnimation.name); //AnimationUtility.SetAnimationClips(newAnimation, clips.ToArray()); //AssetDatabase.CreateAsset(newAnimation, path + "/combined.fbx"); //AssetDatabase.CreateAsset(clips, path + "/testasset.fbx"); }
public void TestExporterCallbacks() { var tree = CreateHierarchy(); var tester = new CallbackTester(tree.transform, GetRandomFbxFilePath()); var n = tree.GetComponentsInChildren <Transform>().Length; // No callbacks registered => no calls. tester.Verify(0, 0); ModelExporter.RegisterMeshObjectCallback(tester.CallbackForObject); ModelExporter.RegisterMeshCallback <FbxPrefab>(tester.CallbackForFbxPrefab); // No fbprefab => no component calls, but every object called. tester.Verify(0, n); // Add a fbxprefab, check every object called and the prefab called. tree.transform.Find("Parent1").gameObject.AddComponent <FbxPrefab>(); tester.Verify(1, n); // Make the object report it's replacing everything => no component calls. tester.Verify(0, n, objectResult: true); // Make sure we can't register for a component twice, but we can // for an object. Register twice for an object means two calls per // object. Assert.That(() => ModelExporter.RegisterMeshCallback <FbxPrefab>(tester.CallbackForFbxPrefab), Throws.Exception); ModelExporter.RegisterMeshObjectCallback(tester.CallbackForObject); tester.Verify(1, 2 * n); // Register twice but return true => only one call per object. tester.Verify(0, n, objectResult: true); // Unregister once => only one call per object, and no more for the prefab. ModelExporter.UnRegisterMeshCallback <FbxPrefab>(); ModelExporter.UnRegisterMeshObjectCallback(tester.CallbackForObject); tester.Verify(0, n); // Legal to unregister if already unregistered. ModelExporter.UnRegisterMeshCallback <FbxPrefab>(); tester.Verify(0, n); // Register same callback twice gets back to original state. ModelExporter.UnRegisterMeshObjectCallback(tester.CallbackForObject); tester.Verify(0, 0); // Legal to unregister if already unregistered. ModelExporter.UnRegisterMeshObjectCallback(tester.CallbackForObject); tester.Verify(0, 0); /////////////////////// // Test that the callbacks not only get called, but can replace // meshes. var sphere = GameObject.CreatePrimitive(PrimitiveType.Capsule); var sphereMesh = sphere.GetComponent <MeshFilter>().sharedMesh; var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); var cubeMesh = cube.GetComponent <MeshFilter>().sharedMesh; // Pick on the fbxprefab to output a sphere in place of the cube. // the fbxprefab is on parent1. string filename; GameObject asset; Mesh assetMesh; GetMeshForComponent <FbxPrefab> prefabCallback = (ModelExporter exporter, FbxPrefab component, FbxNode node) => { exporter.ExportMesh(sphereMesh, node); return(true); }; ModelExporter.RegisterMeshCallback(prefabCallback); filename = GetRandomFbxFilePath(); ModelExporter.ExportObject(filename, tree); ModelExporter.UnRegisterMeshCallback <FbxPrefab>(); asset = AssetDatabase.LoadMainAssetAtPath(filename) as GameObject; assetMesh = asset.transform.Find("Parent1").GetComponent <MeshFilter>().sharedMesh; Assert.AreEqual(sphereMesh.triangles.Length, assetMesh.triangles.Length); assetMesh = asset.transform.Find("Parent2").GetComponent <MeshFilter>().sharedMesh; Assert.AreEqual(cubeMesh.triangles.Length, assetMesh.triangles.Length); // Try again, but this time pick on Parent2 by name (different just // to make sure we don't pass if the previous pass didn't // actually unregister). filename = GetRandomFbxFilePath(); GetMeshForObject callback = (ModelExporter exporter, GameObject gameObject, FbxNode node) => { if (gameObject.name == "Parent2") { exporter.ExportMesh(sphereMesh, node); return(true); } else { return(false); } }; ModelExporter.RegisterMeshObjectCallback(callback); ModelExporter.ExportObject(filename, tree); ModelExporter.UnRegisterMeshObjectCallback(callback); asset = AssetDatabase.LoadMainAssetAtPath(filename) as GameObject; assetMesh = asset.transform.Find("Parent1").GetComponent <MeshFilter>().sharedMesh; Assert.AreEqual(cubeMesh.triangles.Length, assetMesh.triangles.Length); assetMesh = asset.transform.Find("Parent2").GetComponent <MeshFilter>().sharedMesh; Assert.AreEqual(sphereMesh.triangles.Length, assetMesh.triangles.Length); }
public void TestBasics() { Assert.That(!string.IsNullOrEmpty(ModelExporter.GetVersionFromReadme())); // Test GetOrCreateLayer using (var fbxManager = FbxManager.Create()) { var fbxMesh = FbxMesh.Create(fbxManager, "name"); var layer0 = ModelExporter.GetOrCreateLayer(fbxMesh); Assert.That(layer0, Is.Not.Null); Assert.That(ModelExporter.GetOrCreateLayer(fbxMesh), Is.EqualTo(layer0)); var layer5 = ModelExporter.GetOrCreateLayer(fbxMesh, layer: 5); Assert.That(layer5, Is.Not.Null); Assert.That(layer5, Is.Not.EqualTo(layer0)); } // Test axis conversion: a x b in left-handed is the same as b x a // in right-handed (that's why we need to flip the winding order). var a = new Vector3(1, 0, 0); var b = new Vector3(0, 0, 1); var crossLeft = Vector3.Cross(a, b); Assert.That(ModelExporter.DefaultMaterial); // Test non-static functions. using (var fbxManager = FbxManager.Create()) { var fbxScene = FbxScene.Create(fbxManager, "scene"); var fbxNode = FbxNode.Create(fbxScene, "node"); var exporter = new ModelExporter(); // Test ExportMaterial: it exports and it re-exports bool result = exporter.ExportMaterial(ModelExporter.DefaultMaterial, fbxScene, fbxNode); Assert.IsTrue(result); var fbxMaterial = fbxNode.GetMaterial(0); Assert.That(fbxMaterial, Is.Not.Null); result = exporter.ExportMaterial(ModelExporter.DefaultMaterial, fbxScene, fbxNode); var fbxMaterial2 = fbxNode.GetMaterial(1); Assert.AreEqual(fbxMaterial, fbxMaterial2); // Test ExportTexture: it finds the same texture for the default-material (it doesn't create a new one) var fbxMaterialNew = FbxSurfaceLambert.Create(fbxScene, "lambert"); exporter.ExportTexture(ModelExporter.DefaultMaterial, "_MainTex", fbxMaterialNew, FbxSurfaceLambert.sBump); Assert.AreEqual( fbxMaterial.FindProperty(FbxSurfaceLambert.sDiffuse).GetSrcObject(), fbxMaterialNew.FindProperty(FbxSurfaceLambert.sBump).GetSrcObject() ); // Test ExportMesh: make sure we exported a mesh with welded vertices. var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); var cubeNode = FbxNode.Create(fbxScene, "cube"); exporter.ExportMesh(cube.GetComponent <MeshFilter>().sharedMesh, cubeNode); Assert.That(cubeNode.GetMesh(), Is.Not.Null); Assert.That(cubeNode.GetMesh().GetControlPointsCount(), Is.EqualTo(8)); } // Test exporting a skinned-mesh. Make sure it doesn't leak (it did at one point) { var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); var character = new GameObject(); var smr = character.AddComponent <SkinnedMeshRenderer>(); smr.sharedMesh = cube.GetComponent <MeshFilter>().sharedMesh; var meshCount = Object.FindObjectsOfType <Mesh>().Length; ModelExporter.ExportObject(GetRandomFbxFilePath(), character); Assert.AreEqual(meshCount, Object.FindObjectsOfType <Mesh>().Length); } // Test euler to quaternion conversion { // EulerToQuaternionZXY var v = new Vector3(50, 45, 190); var quat = ModelExporter.EulerToQuaternionZXY(v); var unityQuat = Quaternion.Euler(v); Assert.That((float)quat.X, Is.EqualTo(unityQuat.x)); Assert.That((float)quat.Y, Is.EqualTo(unityQuat.y)); Assert.That((float)quat.Z, Is.EqualTo(unityQuat.z)); Assert.That((float)quat.W, Is.EqualTo(unityQuat.w)); // EulerToQuaternionXYZ var fbxV = new FbxVector4(v.x, v.y, v.z); var xyzQuat = ModelExporter.EulerToQuaternionXYZ(fbxV); // get the vector from the quaternion FbxAMatrix m = new FbxAMatrix(); m.SetR(fbxV); var actualQuat = m.GetQ(); // since this quaternion is XYZ instead of ZXY, it should not match the quaternion // created with EulerToQuaternionZXY Assert.That(xyzQuat, Is.Not.EqualTo(quat)); Assert.That(xyzQuat, Is.EqualTo(actualQuat)); } }
public void TestBasics() { Assert.That(!string.IsNullOrEmpty(ModelExporter.GetVersionFromReadme())); // Test GetOrCreateLayer using (var fbxManager = FbxManager.Create()) { var fbxMesh = FbxMesh.Create(fbxManager, "name"); var layer0 = ModelExporter.GetOrCreateLayer(fbxMesh); Assert.That(layer0, Is.Not.Null); Assert.That(ModelExporter.GetOrCreateLayer(fbxMesh), Is.EqualTo(layer0)); var layer5 = ModelExporter.GetOrCreateLayer(fbxMesh, layer: 5); Assert.That(layer5, Is.Not.Null); Assert.That(layer5, Is.Not.EqualTo(layer0)); } // Test axis conversion: a x b in left-handed is the same as b x a // in right-handed (that's why we need to flip the winding order). var a = new Vector3(1, 0, 0); var b = new Vector3(0, 0, 1); var crossLeft = Vector3.Cross(a, b); var afbx = ModelExporter.ConvertToRightHanded(a); var bfbx = ModelExporter.ConvertToRightHanded(b); Assert.AreEqual(ModelExporter.ConvertToRightHanded(crossLeft), bfbx.CrossProduct(afbx)); // Test scale conversion. Nothing complicated here... var afbxPosition = ModelExporter.ConvertToRightHanded(a, ModelExporter.UnitScaleFactor); Assert.AreEqual(100, afbxPosition.Length()); // Test rotation conversion. var q = Quaternion.Euler(new Vector3(0, 90, 0)); var fbxAngles = ModelExporter.ConvertQuaternionToXYZEuler(q); Assert.AreEqual(fbxAngles.X, 0); Assert.That(fbxAngles.Y, Is.InRange(-90.001, -89.999)); Assert.AreEqual(fbxAngles.Z, 0); Assert.That(ModelExporter.DefaultMaterial); // Test non-static functions. using (var fbxManager = FbxManager.Create()) { var fbxScene = FbxScene.Create(fbxManager, "scene"); var fbxNode = FbxNode.Create(fbxScene, "node"); var exporter = new ModelExporter(); // Test ExportMaterial: it exports and it re-exports bool result = exporter.ExportMaterial(ModelExporter.DefaultMaterial, fbxScene, fbxNode); Assert.IsTrue(result); var fbxMaterial = fbxNode.GetMaterial(0); Assert.That(fbxMaterial, Is.Not.Null); result = exporter.ExportMaterial(ModelExporter.DefaultMaterial, fbxScene, fbxNode); var fbxMaterial2 = fbxNode.GetMaterial(1); Assert.AreEqual(fbxMaterial, fbxMaterial2); // Test ExportTexture: it finds the same texture for the default-material (it doesn't create a new one) var fbxMaterialNew = FbxSurfaceLambert.Create(fbxScene, "lambert"); exporter.ExportTexture(ModelExporter.DefaultMaterial, "_MainTex", fbxMaterialNew, FbxSurfaceLambert.sBump); Assert.AreEqual( fbxMaterial.FindProperty(FbxSurfaceLambert.sDiffuse).GetSrcObject(), fbxMaterialNew.FindProperty(FbxSurfaceLambert.sBump).GetSrcObject() ); // Test ExportMesh: make sure we exported a mesh with welded vertices. var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); var cubeNode = FbxNode.Create(fbxScene, "cube"); exporter.ExportMesh(cube.GetComponent <MeshFilter>().sharedMesh, cubeNode); Assert.That(cubeNode.GetMesh(), Is.Not.Null); Assert.That(cubeNode.GetMesh().GetControlPointsCount(), Is.EqualTo(8)); } // Test exporting a skinned-mesh. Make sure it doesn't leak (it did at one point) { var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); var character = new GameObject(); var smr = character.AddComponent <SkinnedMeshRenderer>(); smr.sharedMesh = cube.GetComponent <MeshFilter>().sharedMesh; var meshCount = Object.FindObjectsOfType <Mesh>().Length; ModelExporter.ExportObject(GetRandomFbxFilePath(), character); Assert.AreEqual(meshCount, Object.FindObjectsOfType <Mesh>().Length); } }