static void InitExportableObjects(GameObject go, ExportContext context) { var smr = go.GetComponent<SkinnedMeshRenderer>(); var mr = go.GetComponent<MeshRenderer>(); var mf = go.GetComponent<MeshFilter>(); var cam = go.GetComponent<Camera>(); Transform expRoot = context.exportRoot; var tmpPath = new pxr.SdfPath(UnityTypeConverter.GetPath(go.transform, expRoot)); while (!tmpPath.IsRootPrimPath()) { tmpPath = tmpPath.GetParentPath(); } // TODO: What if this path is in use? string materialBasePath = tmpPath.ToString() + "/Materials/"; // Ensure the "Materials" prim is defined with a valid prim type. context.scene.Write(materialBasePath.TrimEnd('/'), new ScopeSample()); if (smr != null) { foreach (var mat in smr.sharedMaterials) { if (!context.matMap.ContainsKey(mat)) { string usdPath = materialBasePath + pxr.UsdCs.TfMakeValidIdentifier(mat.name + "_" + mat.GetInstanceID().ToString()); context.matMap.Add(mat, usdPath); } } CreateExportPlan(go, CreateSample<MeshSample>(context), MeshExporter.ExportSkinnedMesh, context); CreateExportPlan(go, CreateSample<MeshSample>(context), NativeExporter.ExportObject, context, insertFirst: false); if (smr.rootBone == null) { Debug.LogWarning("No root bone at: " + UnityTypeConverter.GetPath(go.transform, expRoot)); } else if (smr.bones == null || smr.bones.Length == 0) { Debug.LogWarning("No bones at: " + UnityTypeConverter.GetPath(go.transform, expRoot)); } else { // Each mesh in a model may have a different root bone, which now must be merged into a // single skeleton for export to USD. try { MergeBonesSimple(smr.transform, smr.rootBone, smr.bones, smr.sharedMesh.bindposes, context); } catch (Exception ex) { Debug.LogException(new Exception("Failed to merge bones for " + UnityTypeConverter.GetPath(smr.transform), ex)); } } } else if (mf != null && mr != null) { foreach (var mat in mr.sharedMaterials) { if (mat == null) { continue; } if (!context.matMap.ContainsKey(mat)) { string usdPath = materialBasePath + pxr.UsdCs.TfMakeValidIdentifier(mat.name + "_" + mat.GetInstanceID().ToString()); context.matMap.Add(mat, usdPath); } } CreateExportPlan(go, CreateSample<MeshSample>(context), MeshExporter.ExportMesh, context); CreateExportPlan(go, CreateSample<MeshSample>(context), NativeExporter.ExportObject, context, insertFirst: false); } else if (cam) { CreateExportPlan(go, CreateSample<CameraSample>(context), CameraExporter.ExportCamera, context); CreateExportPlan(go, CreateSample<CameraSample>(context), NativeExporter.ExportObject, context, insertFirst: false); } }
public static void Export(GameObject root, ExportContext context, bool zeroRootTransform) { // Remove parent transform effects while exporting. // This must be restored before returning from this function. var parent = root.transform.parent; if (zeroRootTransform) { root.transform.SetParent(null, worldPositionStays: false); } // Also zero out and restore local rotations on the root. var localPos = root.transform.localPosition; var localRot = root.transform.localRotation; var localScale = root.transform.localScale; if (zeroRootTransform) { root.transform.localPosition = Vector3.zero; root.transform.localRotation = Quaternion.identity; root.transform.localScale = Vector3.one; } // Scale overall scene for export (e.g. USDZ export needs scale 100) root.transform.localScale *= context.scale; UnityEngine.Profiling.Profiler.BeginSample("USD: Export"); try { ExportImpl(root, context); var path = new pxr.SdfPath(UnityTypeConverter.GetPath(root.transform)); var prim = context.scene.Stage.GetPrimAtPath(path); if (prim) { context.scene.Stage.SetDefaultPrim(prim); } } finally { if (zeroRootTransform) { root.transform.localPosition = localPos; root.transform.localRotation = localRot; root.transform.localScale = localScale; root.transform.SetParent(parent, worldPositionStays: false); } else { root.transform.localScale = localScale; } UnityEngine.Profiling.Profiler.EndSample(); } }
public static void ExportSkelRoot(ObjectContext objContext, ExportContext exportContext) { var sample = (SkelRootSample)objContext.sample; // Compute bounds for the root, required by USD. bool first = true; // Ensure the bounds are computed in root-local space. // This is required because USD expects the extent to be a local bound for the SkelRoot. var oldParent = objContext.gameObject.transform.parent; objContext.gameObject.transform.SetParent(null, worldPositionStays: false); try { foreach (var r in objContext.gameObject.GetComponentsInChildren <Renderer>()) { if (first) { // Ensure the bounds object starts growing from the first valid child bounds. first = false; sample.extent = r.bounds; } else { sample.extent.Encapsulate(r.bounds); } } } finally { // Restore the root parent. objContext.gameObject.transform.SetParent(oldParent, worldPositionStays: false); } // Convert handedness if needed. if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { sample.extent.center = UnityTypeConverter.ChangeBasis(sample.extent.center); } // Convert the transform var path = new pxr.SdfPath(objContext.path); // If exporting for Z-Up, rotate the world. bool correctZUp = exportContext.scene.UpAxis == Scene.UpAxes.Z; sample.transform = XformExporter.GetLocalTransformMatrix( objContext.gameObject.transform, correctZUp, path.IsRootPrimPath(), exportContext.basisTransform); exportContext.scene.Write(objContext.path, sample); }
SkeletonSample ReadUsdSkeleton(Scene scene, out string skelRootPath) { skelRootPath = null; if (scene == null) { return(null); } try { var path = new pxr.SdfPath(m_usdMeshPath); var absRoot = pxr.SdfPath.AbsoluteRootPath(); var skelRelName = new pxr.TfToken("skel:skeleton"); while (path != absRoot) { var prim = scene.GetPrimAtPath(path); path = path.GetParentPath(); if (prim.HasRelationship(skelRelName)) { var targets = prim.GetRelationship(skelRelName).GetForwardedTargets(); if (targets.Count == 0) { Debug.LogWarning("skel:skeleton has no targets at path: " + prim.GetPath()); continue; } var skelTarget = scene.GetPrimAtPath(targets[0]); if (skelTarget == null) { Debug.LogWarning("prim <" + prim.GetPath() + "> has skel:skeleton with missing target path: " + targets[0]); continue; } skelRootPath = skelTarget.GetPath().ToString(); var sample = new SkeletonSample(); scene.Read(skelTarget.GetPath(), sample); return(sample); } } Debug.LogWarning("Skeleton not found for path: " + m_usdMeshPath); return(null); } catch (System.Exception ex) { Debug.LogException(ex); return(null); } }
/// <summary> /// Copies the transform value from USD to Unity, optionally changing handedness in the /// process. /// </summary> public static void BuildXform(pxr.SdfPath path, XformableSample usdXf, GameObject go, SceneImportOptions options, Scene scene) { // If there is an access mask and it's not initially being populated, check to see if the // transform for this object actually varies over time, if not, we can simply return. if (scene.AccessMask != null && !scene.IsPopulatingAccessMask) { System.Reflection.MemberInfo transformMember = null; transformMember = usdXf.GetType().GetMember("transform")[0]; HashSet <System.Reflection.MemberInfo> members; if (!scene.AccessMask.Included.TryGetValue(path, out members) || !members.Contains(transformMember)) { return; } } BuildXform(usdXf.transform, go, options); }
public static void ExportXform(ObjectContext objContext, ExportContext exportContext) { UnityEngine.Profiling.Profiler.BeginSample("USD: Xform Conversion"); XformSample sample = (XformSample)objContext.sample; var localRot = objContext.gameObject.transform.localRotation; var localScale = objContext.gameObject.transform.localScale; var path = new pxr.SdfPath(objContext.path); // If exporting for Z-Up, rotate the world. bool correctZUp = exportContext.scene.UpAxis == Scene.UpAxes.Z; sample.transform = GetLocalTransformMatrix( objContext.gameObject.transform, correctZUp, path.IsRootPrimPath(), exportContext.basisTransform); UnityEngine.Profiling.Profiler.EndSample(); UnityEngine.Profiling.Profiler.BeginSample("USD: Xform Write"); exportContext.scene.Write(objContext.path, sample); UnityEngine.Profiling.Profiler.EndSample(); }
// // Start generates a USD scene procedurally, containing a single cube with a material, shader // and texture bound. It then inspects the cube to discover the material. A Unity material is // constructed and the parameters are copied in a generic way. Similarly, the texture is // discovered and loaded as a Unity Texture2D and bound to the material. // // Also See: https://docs.unity3d.com/Manual/MaterialsAccessingViaScript.html // void Start() { // Create a scene for this test, but could also be read from disk. Scene usdScene = CreateSceneWithShading(); // Read the material and shader ID. var usdMaterial = new MaterialSample(); string shaderId; // ReadMaterial was designed for Unity and assumes there is one "surface" shader bound. if (!MaterialSample.ReadMaterial(usdScene, kCubePath, usdMaterial, out shaderId)) { throw new System.Exception("Failed to read material"); } // Map the shader ID to the corresponding Unity/USD shader pair. ShaderPair shader; if (shaderId == null || !m_shaderMap.TryGetValue(shaderId, out shader)) { throw new System.Exception("Material had no surface bound"); } // // Read and process the shader-specific parameters. // // UsdShade requires all connections target an attribute, but we actually want to deserialize // the entire prim, so we get just the prim path here. var shaderPath = new pxr.SdfPath(usdMaterial.surface.connectedPath).GetPrimPath(); usdScene.Read(shaderPath, shader.usdShader); // // Construct material & process the inputs, textures, and keywords. // var mat = new UnityEngine.Material(shader.unityShader); // Apply material keywords. foreach (string keyword in usdMaterial.requiredKeywords ?? new string[0]) { mat.EnableKeyword(keyword); } // Iterate over all input parameters and copy values and/or construct textures. foreach (var param in shader.usdShader.GetInputParameters()) { if (!SetMaterialParameter(mat, param.unityName, param.value)) { throw new System.Exception("Incompatible shader data type: " + param.ToString()); } } foreach (var param in shader.usdShader.GetInputTextures()) { if (string.IsNullOrEmpty(param.connectedPath)) { // Not connected to a texture. continue; } // Only 2D textures are supported in this example. var usdTexture = new Texture2DSample(); // Again, we want the prim path, not the attribute path. var texturePath = new pxr.SdfPath(param.connectedPath).GetPrimPath(); usdScene.Read(texturePath, usdTexture); // This example also only supports explicit sourceFiles, they cannot be connected. if (string.IsNullOrEmpty(usdTexture.sourceFile.defaultValue)) { continue; } // For details, see: https://docs.unity3d.com/Manual/MaterialsAccessingViaScript.html foreach (string keyword in param.requiredShaderKeywords) { mat.EnableKeyword(keyword); } var data = System.IO.File.ReadAllBytes(usdTexture.sourceFile.defaultValue); var unityTex = new Texture2D(2, 2); unityTex.LoadImage(data); mat.SetTexture(param.unityName, unityTex); Debug.Log("Set " + param.unityName + " to " + usdTexture.sourceFile.defaultValue); unityTex.Apply(updateMipmaps: true, makeNoLongerReadable: false); } // // Create and bind the geometry. // // Create a cube and set the material. // Note that geometry is handled minimally here and is incomplete. var cubeSample = new CubeSample(); usdScene.Read(kCubePath, cubeSample); var go = GameObject.CreatePrimitive(PrimitiveType.Cube); go.transform.SetParent(transform, worldPositionStays: false); go.transform.localScale = Vector3.one * (float)cubeSample.size; m_cube = transform; go.GetComponent <MeshRenderer>().material = mat; }
public static void SdfPathEqualityTest() { var A = new pxr.SdfPath("/Foo"); var B = new pxr.SdfPath("/Foo"); var C = new pxr.SdfPath("/Foo/Bar"); AssertEqual(A, A); AssertEqual(B, B); AssertEqual(C, C); AssertEqual(A, B); AssertEqual(A, C.GetParentPath()); AssertEqual(C.GetParentPath(), A); AssertEqual(A.GetHashCode(), A.GetHashCode()); AssertEqual(B.GetHashCode(), B.GetHashCode()); AssertEqual(C.GetHashCode(), C.GetHashCode()); AssertEqual(A.GetHashCode(), B.GetHashCode()); AssertEqual(A.GetHashCode(), C.GetParentPath().GetHashCode()); AssertEqual(C.GetParentPath().GetHashCode(), A.GetHashCode()); AssertEqual(A.GetHash(), A.GetHash()); AssertEqual(B.GetHash(), B.GetHash()); AssertEqual(C.GetHash(), C.GetHash()); AssertEqual(A.GetHash(), B.GetHash()); AssertEqual(A.GetHash(), C.GetParentPath().GetHash()); AssertEqual(C.GetParentPath().GetHash(), A.GetHash()); AssertEqual(A.ToString(), A.ToString()); AssertEqual(B.ToString(), B.ToString()); AssertEqual(C.ToString(), C.ToString()); AssertEqual(A.ToString(), B.ToString()); AssertEqual(A.ToString(), C.GetParentPath().ToString()); AssertEqual(C.GetParentPath().ToString(), A.ToString()); var hashSet = new HashSet <pxr.SdfPath>(); hashSet.Add(A); AssertTrue(hashSet.Contains(A)); AssertTrue(hashSet.Contains(B)); AssertTrue(hashSet.Contains(C.GetParentPath())); AssertTrue(hashSet.Remove(B)); hashSet.Add(B); AssertTrue(hashSet.Contains(A)); AssertTrue(hashSet.Contains(B)); AssertTrue(hashSet.Contains(C.GetParentPath())); AssertTrue(hashSet.Remove(C.GetParentPath())); var dict = new Dictionary <pxr.SdfPath, string>(); dict.Add(A, A.GetString()); AssertTrue(dict.ContainsKey(A)); AssertTrue(dict.ContainsKey(B)); AssertTrue(dict.ContainsKey(C.GetParentPath())); AssertTrue(dict.Remove(B)); dict.Add(B, B.GetString()); AssertTrue(dict.ContainsKey(A)); AssertTrue(dict.ContainsKey(B)); AssertTrue(dict.ContainsKey(C.GetParentPath())); AssertTrue(dict.Remove(C.GetParentPath())); }
/// <summary> /// Writes SerializedProperty to USD, traversing all nested properties. /// </summary> static void PropertyToUsd(string path, string propPrefix, Scene scene, SerializedProperty prop, System.Text.StringBuilder sb) { string prefix = ""; try { var nameStack = new List <string>(); nameStack.Add("unity"); if (!string.IsNullOrEmpty(propPrefix)) { nameStack.Add(propPrefix); } string lastName = ""; int lastDepth = 0; while (prop.Next(prop.propertyType == SerializedPropertyType.Generic && !prop.isArray)) { string tabIn = ""; for (int i = 0; i < prop.depth; i++) { tabIn += " "; } if (prop.depth > lastDepth) { Debug.Assert(lastName != ""); nameStack.Add(lastName); } else if (prop.depth < lastDepth) { nameStack.RemoveRange(nameStack.Count - (lastDepth - prop.depth), lastDepth - prop.depth); } lastDepth = prop.depth; lastName = prop.name; if (nameStack.Count > 0) { prefix = string.Join(":", nameStack.ToArray()); prefix += ":"; } else { prefix = ""; } sb.Append(tabIn + prefix + prop.name + "[" + prop.propertyType.ToString() + "] = "); if (prop.isArray && prop.propertyType != SerializedPropertyType.String) { // TODO. sb.AppendLine("ARRAY"); } else if (prop.propertyType == SerializedPropertyType.Generic) { sb.AppendLine("Generic"); } else if (prop.propertyType == SerializedPropertyType.AnimationCurve || prop.propertyType == SerializedPropertyType.Gradient) { // TODO. sb.AppendLine(NativeSerialization.ValueToString(prop)); } else { sb.AppendLine(NativeSerialization.ValueToString(prop)); var vtValue = NativeSerialization.PropToVtValue(prop); var primPath = new pxr.SdfPath(path); var attrName = new pxr.TfToken(prefix + prop.name); /* * var oldPrim = context.prevScene.Stage.GetPrimAtPath(primPath); * pxr.VtValue oldVtValue = null; * if (oldPrim.IsValid()) { * var oldAttr = oldPrim.GetAttribute(attrName); * if (oldAttr.IsValid()) { * oldVtValue = oldAttr.Get(0); * } * } * * if (oldVtValue != null && vtValue == oldVtValue) { * Debug.Log("skipping: " + prop.name); * continue; * } */ var sdfType = NativeSerialization.GetSdfType(prop); var prim = scene.GetPrimAtPath(primPath); var attr = prim.CreateAttribute(attrName, sdfType); attr.Set(vtValue); } } } catch { Debug.LogWarning("Failed on: " + path + "." + prefix + prop.name); throw; } }
// -------------------------------------------------------------------------------------------- // // Export Logic // -------------------------------------------------------------------------------------------- // /// Exports either all brush strokes or the given selection to the specified file. static public void ExportPayload(string outputFile) { // Would be nice to find a way to kick this off automatically. // Redundant calls are ignored. if (!InitUsd.Initialize()) { return; } // Unity is left handed (DX), USD is right handed (GL) var payload = ExportCollector.GetExportPayload(AxisConvention.kUsd); var brushCatalog = BrushCatalog.m_Instance; // The Scene object provids serialization methods arbitrary C# objects to USD. USD.NET.Scene scene = USD.NET.Scene.Create(outputFile); // The target time at which samples will be written. // // In this case, all data is being written to the "default" time, which means it can be // overridden by animated values later. scene.Time = null; // Bracketing times to specify the valid animation range. scene.StartTime = 1.0; scene.EndTime = 1.0; const string kGeomName = "/Geom"; const string kCurvesName = "/Curves"; string path = ""; AddSketchRoot(scene, GetSketchPath()); // Create: </Sketch> CreateXform(scene, GetStrokesPath()); // Create: </Sketch/Strokes> CreateXform(scene, GetModelsPath()); // Create: </Sketch/Models> // Main export loop. try { foreach (ExportUtils.GroupPayload group in payload.groups) { // Example: </Sketch/Strokes/Group_0> path = GetGroupPath(group.id); CreateXform(scene, path); // Example: </Sketch/Strokes/Group_0/Geom> CreateXform(scene, path + kGeomName); // Example: </Sketch/Strokes/Group_0/Curves> CreateXform(scene, path + kCurvesName); int iBrushMeshPayload = -1; foreach (var brushMeshPayload in group.brushMeshes) { ++iBrushMeshPayload; // Conditionally moves Normal into Texcoord1 so that the normal semantic is respected. // This only has an effect when layout.bFbxExportNormalAsTexcoord1 == true. // Note that this modifies the GeometryPool in place. FbxUtils.ApplyFbxTexcoordHack(brushMeshPayload.geometry); // Brushes are expected to be batched by type/GUID. Guid brushGuid = brushMeshPayload.strokes[0].m_BrushGuid; string brushName = "/" + SanitizeIdentifier(brushCatalog.GetBrush(brushGuid).DurableName) + "_"; // Example: </Sketch/Strokes/Group_0/Geom/Marker_0> string meshPath = path + kGeomName + brushName; // Example: </Sketch/Strokes/Group_0/Curves/Marker_0> string curvePath = path + kCurvesName + brushName; var geomPool = brushMeshPayload.geometry; var strokes = brushMeshPayload.strokes; var mat44 = Matrix4x4.identity; var meshPrimPath = new pxr.SdfPath(meshPath + iBrushMeshPayload.ToString()); var curvesPrimPath = new pxr.SdfPath(curvePath + iBrushMeshPayload.ToString()); // // Geometry // BrushSample brushSample = GetBrushSample(geomPool, strokes, mat44); // Write the BrushSample to the same point in the scenegraph at which it exists in Tilt // Brush. Notice this method is Async, it is queued to a background thread to perform I/O // which means it is not safe to read from the scene until WaitForWrites() is called. scene.Write(meshPrimPath, brushSample); // // Stroke Curves // var curvesSample = GetCurvesSample(payload, strokes, Matrix4x4.identity); scene.Write(curvesPrimPath, curvesSample); // // Materials // double?oldTime = scene.Time; scene.Time = null; string materialPath = CreateMaterialNetwork( scene, brushMeshPayload.exportableMaterial, GetStrokesPath()); BindMaterial(scene, meshPrimPath.ToString(), materialPath); BindMaterial(scene, curvesPrimPath.ToString(), materialPath); scene.Time = oldTime; } } // // Models // var knownModels = new Dictionary <Model, string>(); int iModelMeshPayload = -1; foreach (var modelMeshPayload in payload.modelMeshes) { ++iModelMeshPayload; var modelId = modelMeshPayload.modelId; var modelNamePrefix = "/Model_" + SanitizeIdentifier(modelMeshPayload.model.GetExportName()) + "_"; var modelName = modelNamePrefix + modelId; var xf = modelMeshPayload.xform; // Geometry pools may be repeated and should be turned into references. var geomPool = modelMeshPayload.geometry; var modelRootPath = new pxr.SdfPath(GetModelsPath() + modelName); // Example: </Sketch/Models/Model_Andy_0> CreateXform(scene, modelRootPath, xf); // Example: </Sketch/Models/Model_Andy_0/Geom> CreateXform(scene, modelRootPath + kGeomName); string modelPathToReference; if (knownModels.TryGetValue(modelMeshPayload.model, out modelPathToReference) && modelPathToReference != modelRootPath) { // Create an Xform, note that the world transform here will override the referenced model. var meshXf = new MeshXformSample(); meshXf.transform = xf; scene.Write(modelRootPath, meshXf); // Add a USD reference to previously created model. var prim = scene.Stage.GetPrimAtPath(modelRootPath); prim.GetReferences().AddReference("", new pxr.SdfPath(modelPathToReference)); continue; } // Example: </Sketch/Models/Geom/Model_Andy_0/Mesh_0> path = modelRootPath + kGeomName + "/Mesh_" + iModelMeshPayload.ToString(); var meshPrimPath = new pxr.SdfPath(path); var meshSample = new MeshSample(); GetMeshSample(geomPool, Matrix4x4.identity, meshSample); scene.Write(path, meshSample); scene.Stage.GetPrimAtPath(new pxr.SdfPath(path)).SetInstanceable(true); // // Materials // // Author at default time. double?oldTime = scene.Time; scene.Time = null; // Model materials must live under the model root, since we will reference the model. string materialPath = CreateMaterialNetwork( scene, modelMeshPayload.exportableMaterial, modelRootPath); BindMaterial(scene, meshPrimPath.ToString(), materialPath); // Continue authoring at the desired time index. scene.Time = oldTime; // // Setup to be referenced. // if (!knownModels.ContainsKey(modelMeshPayload.model)) { knownModels.Add(modelMeshPayload.model, modelRootPath); } } } catch { scene.Save(); scene.Close(); throw; } // Save will force a sync with all async reads and writes. scene.Save(); scene.Close(); }
private static string GetObjectName(pxr.SdfPath rootPrimName, string path) { return(pxr.UsdCs.TfIsValidIdentifier(rootPrimName.GetName()) ? rootPrimName.GetName() : GetObjectName(path)); }
public static Texture2D ImportConnectedTexture <T>(Scene scene, Connectable <T> connection, bool isNormalMap, SceneImportOptions options, out string uvPrimvar) { uvPrimvar = null; // TODO: look for the expected texture/primvar reader pair. var textureSample = new TextureReaderSample(); var connectedPrimPath = scene.GetSdfPath(connection.connectedPath).GetPrimPath(); Texture2D result = null; scene.Read(connectedPrimPath, textureSample); if (textureSample.file.defaultValue != null && !string.IsNullOrEmpty(textureSample.file.defaultValue.GetResolvedPath())) { if (OnResolveTexture != null) { result = OnResolveTexture(textureSample.file.defaultValue, isNormalMap, options); } else { result = DefaultTextureResolver(textureSample.file.defaultValue, isNormalMap, options); } } Connectable <Vector2> st = textureSample.st; if (st != null && st.IsConnected() && !string.IsNullOrEmpty(st.connectedPath)) { var pvSrc = new PrimvarReaderSample <Vector2>(); scene.Read(new pxr.SdfPath(textureSample.st.connectedPath).GetPrimPath(), pvSrc); if (pvSrc.varname != null) { if (pvSrc.varname.IsConnected()) { var connPath = new pxr.SdfPath(pvSrc.varname.GetConnectedPath()); var attr = scene.GetAttributeAtPath(connPath); if (attr != null) { var value = attr.Get(scene.Time); uvPrimvar = pxr.UsdCs.VtValueToTfToken(value).ToString(); } else { Debug.LogWarning("No primvar name was provided at the connected path: " + connPath); uvPrimvar = ""; } } else if (pvSrc.varname.defaultValue != null) { // Ask the mesh importer to load the specified texcoord. // This must be a callback, since materials-to-meshes are one-to-many. uvPrimvar = pvSrc.varname.defaultValue; } } } return(result); }
/// <summary> /// Rebuilds the USD scene as Unity GameObjects, with a limited budget per update. /// </summary> public static IEnumerator BuildScene(Scene scene, GameObject root, SceneImportOptions importOptions, PrimMap primMap, float targetFrameMilliseconds, bool composingSubtree) { var timer = new System.Diagnostics.Stopwatch(); var usdPrimRoot = new pxr.SdfPath(importOptions.usdRootPath); // Setting an arbitrary fudge factor of 20% is very non-scientific, however it's better than // nothing. The correct way to hit a deadline is to predict how long each iteration actually // takes and then return early if the estimated time is over budget. float targetTime = targetFrameMilliseconds * .8f; timer.Start(); // Reconstruct the USD hierarchy as Unity GameObjects. // A PrimMap is returned for tracking the USD <-> Unity mapping. Profiler.BeginSample("USD: Build Hierarchy"); if (importOptions.importHierarchy || importOptions.forceRebuild) { // When a USD file is fully RE-imported, all exsiting USD data must be removed. The old // assumption was that the root would never have much more than the UsdAsset component // itself, however it's now clear that the root may also have meaningful USD data added // too. // // TODO(jcowles): This feels like a workaround. What we really want here is an "undo" // process for changes made to the root GameObject. For example, to clean up non-USD // components which may have been added (e.g. what if a mesh is imported to the root? // currently the MeshRenderer etc will remain after re-import). RemoveComponent <UsdAssemblyRoot>(root); RemoveComponent <UsdVariantSet>(root); RemoveComponent <UsdModelRoot>(root); RemoveComponent <UsdLayerStack>(root); RemoveComponent <UsdPayload>(root); RemoveComponent <UsdPrimSource>(root); primMap.Clear(); HierarchyBuilder.BuildGameObjects(scene, root, usdPrimRoot, scene.Find(usdPrimRoot.ToString(), "UsdSchemaBase"), primMap, importOptions); } Profiler.EndSample(); if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } Profiler.BeginSample("USD: Post Process Hierarchy"); foreach (var processor in root.GetComponents <IImportPostProcessHierarchy>()) { try { processor.PostProcessHierarchy(primMap, importOptions); } catch (System.Exception ex) { Debug.LogException(ex); } } Profiler.EndSample(); if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } // // Pre-process UsdSkelRoots. // var skelRoots = new List <pxr.UsdSkelRoot>(); if (importOptions.importSkinning) { Profiler.BeginSample("USD: Process UsdSkelRoots"); foreach (var path in primMap.SkelRoots) { try { var skelRootPrim = scene.GetPrimAtPath(path); if (!skelRootPrim) { Debug.LogWarning("SkelRoot prim not found: " + path); continue; } var skelRoot = new pxr.UsdSkelRoot(skelRootPrim); if (!skelRoot) { Debug.LogWarning("SkelRoot prim not SkelRoot type: " + path); continue; } skelRoots.Add(skelRoot); GameObject go = primMap[path]; ImporterBase.GetOrAddComponent <Animator>(go, true); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error pre-processing SkelRoot <" + path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); } // // Import known prim types. // // Materials. Profiler.BeginSample("USD: Build Materials"); if (importOptions.ShouldBindMaterials) { foreach (var pathAndSample in scene.ReadAll <MaterialSample>(primMap.Materials)) { try { var mat = MaterialImporter.BuildMaterial(scene, pathAndSample.path, pathAndSample.sample, importOptions); if (mat != null) { importOptions.materialMap[pathAndSample.path] = mat; } } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing material <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } } Profiler.EndSample(); // // Start threads. // ReadAllJob <XformSample> readXforms; if (importOptions.importTransforms) { readXforms = new ReadAllJob <XformSample>(scene, primMap.Xforms); #if UNITY_2018_1_OR_NEWER readXforms.Schedule(primMap.Xforms.Length, 4); #else readXforms.Run(); #endif } if (importOptions.importMeshes) { ActiveMeshImporter.BeginReading(scene, primMap); } #if UNITY_2018_1_OR_NEWER JobHandle.ScheduleBatchedJobs(); #endif // Xforms. // // Note that we are specifically filtering on XformSample, not Xformable, this way only // Xforms are processed to avoid doing that work redundantly. if (importOptions.importTransforms) { Profiler.BeginSample("USD: Build Xforms"); foreach (var pathAndSample in readXforms) { try { if (pathAndSample.path == usdPrimRoot) { // Never read the xform from the USD root, that will be authored in Unity. continue; } GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } foreach (var pathAndSample in scene.ReadAll <XformSample>(primMap.SkelRoots)) { try { if (pathAndSample.path == usdPrimRoot) { // Never read the xform from the USD root, that will be authored in Unity. continue; } GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } if (importOptions.importSkinning) { foreach (var pathAndSample in scene.ReadAll <XformSample>(primMap.Skeletons)) { try { if (pathAndSample.path == usdPrimRoot) { // Never read the xform from the USD root, that will be authored in Unity. continue; } GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } } Profiler.EndSample(); } // Meshes. if (importOptions.importMeshes) { Profiler.BeginSample("USD: Build Meshes"); IEnumerator it = ActiveMeshImporter.Import(scene, primMap, importOptions); while (it.MoveNext()) { if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); // Cubes. Profiler.BeginSample("USD: Build Cubes"); foreach (var pathAndSample in scene.ReadAll <CubeSample>(primMap.Cubes)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); CubeImporter.BuildCube(pathAndSample.sample, go, importOptions); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing cube <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); } // Cameras. if (importOptions.importCameras) { Profiler.BeginSample("USD: Cameras"); foreach (var pathAndSample in scene.ReadAll <CameraSample>(primMap.Cameras)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); // The camera has many value-type parameters that need to be handled correctly when not // not animated. For now, only the camera transform will animate, until this is fixed. if (scene.AccessMask == null || scene.IsPopulatingAccessMask) { CameraImporter.BuildCamera(pathAndSample.sample, go, importOptions); } } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing camera <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); } // Build out masters for instancing. Profiler.BeginSample("USD: Build Instances"); foreach (var masterRootPath in primMap.GetMasterRootPaths()) { try { Transform masterRootXf = primMap[masterRootPath].transform; // Transforms if (importOptions.importTransforms) { Profiler.BeginSample("USD: Build Xforms"); foreach (var pathAndSample in scene.ReadAll <XformSample>(masterRootPath)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } } foreach (var pathAndSample in scene.ReadAll <XformSample>(masterRootPath)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } } foreach (var pathAndSample in scene.ReadAll <XformSample>(primMap.Skeletons)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } } Profiler.EndSample(); } // Meshes. if (importOptions.importMeshes) { Profiler.BeginSample("USD: Build Meshes"); foreach (var pathAndSample in scene.ReadAll <MeshSample>(masterRootPath)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); var subsets = MeshImporter.ReadGeomSubsets(scene, pathAndSample.path); MeshImporter.BuildMesh(pathAndSample.path, pathAndSample.sample, subsets, go, importOptions); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing mesh <" + pathAndSample.path + ">", ex)); } } Profiler.EndSample(); // Cubes. Profiler.BeginSample("USD: Build Cubes"); foreach (var pathAndSample in scene.ReadAll <CubeSample>(masterRootPath)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); CubeImporter.BuildCube(pathAndSample.sample, go, importOptions); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing cube <" + pathAndSample.path + ">", ex)); } } Profiler.EndSample(); } // Cameras. if (importOptions.importCameras) { Profiler.BeginSample("USD: Build Cameras"); foreach (var pathAndSample in scene.ReadAll <CameraSample>(masterRootPath)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); CameraImporter.BuildCamera(pathAndSample.sample, go, importOptions); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing camera <" + pathAndSample.path + ">", ex)); } } Profiler.EndSample(); } } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing master <" + masterRootPath + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } // Instances. Profiler.EndSample(); // // Post-process dependencies: materials and bones. // Profiler.BeginSample("USD: Process Material Bindings"); try { // TODO: Currently ProcessMaterialBindings runs too long and will go over budget for any // large scene. However, pulling the loop into this code feels wrong in terms of // responsibilities. // Process all material bindings in a single vectorized request. MaterialImporter.ProcessMaterialBindings(scene, importOptions); } catch (System.Exception ex) { Debug.LogException(new ImportException("Failed in ProcessMaterialBindings", ex)); } Profiler.EndSample(); if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } // // SkinnedMesh bone bindings. // if (importOptions.importSkinning) { Profiler.BeginSample("USD: Build Skeletons"); var skeletonSamples = new Dictionary <pxr.SdfPath, SkeletonSample>(); foreach (var skelRoot in skelRoots) { try { var bindings = new pxr.UsdSkelBindingVector(); if (!primMap.SkelBindings.TryGetValue(skelRoot.GetPath(), out bindings)) { Debug.LogWarning("No bindings found skelRoot: " + skelRoot.GetPath()); } if (bindings.Count == 0) { Debug.LogWarning("No bindings found skelRoot: " + skelRoot.GetPath()); } foreach (var skelBinding in bindings) { // The SkelRoot will likely have a skeleton binding, but it's inherited, so the bound // skeleton isn't actually known until it's queried from the binding. Still, we would // like not to reprocess skeletons redundantly, so skeletons are cached into a // dictionary. Profiler.BeginSample("Build Bind Transforms"); var skelPath = skelBinding.GetSkeleton().GetPath(); SkeletonSample skelSample = null; if (!skeletonSamples.TryGetValue(skelPath, out skelSample)) { skelSample = new SkeletonSample(); Profiler.BeginSample("Read Skeleton"); scene.Read(skelPath, skelSample); Profiler.EndSample(); skeletonSamples.Add(skelPath, skelSample); // Unity uses the inverse bindTransform, since that's actually what's needed for // skinning. Do that once here, so each skinned mesh doesn't need to do it // redundantly. SkeletonImporter.BuildBindTransforms(skelPath, skelSample, importOptions); var bindXforms = new pxr.VtMatrix4dArray(); var prim = scene.GetPrimAtPath(skelPath); var skel = new pxr.UsdSkelSkeleton(prim); Profiler.BeginSample("Get SkelQuery"); pxr.UsdSkelSkeletonQuery skelQuery = primMap.SkelCache.GetSkelQuery(skel); Profiler.EndSample(); Profiler.BeginSample("Get JointWorldBind Transforms"); if (!skelQuery.GetJointWorldBindTransforms(bindXforms)) { throw new ImportException("Failed to compute binding trnsforms for <" + skelPath + ">"); } Profiler.EndSample(); SkeletonImporter.BuildDebugBindTransforms(skelSample, primMap[skelPath], importOptions); } Profiler.EndSample(); if (importOptions.importSkinWeights) { // // Apply skinning weights to each skinned mesh. // Profiler.BeginSample("Apply Skin Weights"); foreach (var skinningQuery in skelBinding.GetSkinningTargetsAsVector()) { var meshPath = skinningQuery.GetPrim().GetPath(); try { var skelBindingSample = new SkelBindingSample(); var goMesh = primMap[meshPath]; scene.Read(meshPath, skelBindingSample); Profiler.BeginSample("Build Skinned Mesh"); SkeletonImporter.BuildSkinnedMesh( meshPath, skelPath, skelSample, skelBindingSample, goMesh, primMap, importOptions); Profiler.EndSample(); // In terms of performance, this is almost free. goMesh.GetComponent <SkinnedMeshRenderer>().rootBone = primMap[skelPath].transform.GetChild(0); } catch (System.Exception ex) { Debug.LogException(new ImportException("Error skinning mesh: " + meshPath, ex)); } } Profiler.EndSample(); } } } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing SkelRoot <" + skelRoot.GetPath() + ">", ex)); } } // foreach SkelRoot Profiler.EndSample(); if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } // // Bone transforms. // Profiler.BeginSample("USD: Pose Bones"); foreach (var pathAndSample in skeletonSamples) { var skelPath = pathAndSample.Key; try { var prim = scene.GetPrimAtPath(skelPath); var skel = new pxr.UsdSkelSkeleton(prim); pxr.UsdSkelSkeletonQuery skelQuery = primMap.SkelCache.GetSkelQuery(skel); var joints = skelQuery.GetJointOrder(); var restXforms = new pxr.VtMatrix4dArray(); var time = scene.Time.HasValue ? scene.Time.Value : pxr.UsdTimeCode.Default(); Profiler.BeginSample("Compute Joint Local Transforms"); if (!skelQuery.ComputeJointLocalTransforms(restXforms, time, atRest: false)) { throw new ImportException("Failed to compute bind trnsforms for <" + skelPath + ">"); } Profiler.EndSample(); Profiler.BeginSample("Build Bones"); for (int i = 0; i < joints.size(); i++) { var jointPath = scene.GetSdfPath(joints[i]); if (joints[i] == "/") { jointPath = skelPath; } else if (jointPath.IsAbsolutePath()) { Debug.LogException(new System.Exception("Unexpected absolute joint path: " + jointPath)); jointPath = new pxr.SdfPath(joints[i].ToString().TrimStart('/')); jointPath = skelPath.AppendPath(jointPath); } else { jointPath = skelPath.AppendPath(jointPath); } var goBone = primMap[jointPath]; Profiler.BeginSample("Convert Matrix"); var restXform = UnityTypeConverter.FromMatrix(restXforms[i]); Profiler.EndSample(); Profiler.BeginSample("Build Bone"); SkeletonImporter.BuildSkeletonBone(skelPath, goBone, restXform, joints, importOptions); Profiler.EndSample(); } Profiler.EndSample(); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing SkelRoot <" + skelPath + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); } // // Apply instancing. // if (importOptions.importSceneInstances) { Profiler.BeginSample("USD: Build Scene-Instances"); try { // Build scene instances. InstanceImporter.BuildSceneInstances(primMap, importOptions); } catch (System.Exception ex) { Debug.LogException(new ImportException("Failed in BuildSceneInstances", ex)); } Profiler.EndSample(); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } // Build point instances. if (importOptions.importPointInstances) { Profiler.BeginSample("USD: Build Point-Instances"); // TODO: right now all point instancer data is read, but we only need prototypes and indices. foreach (var pathAndSample in scene.ReadAll <PointInstancerSample>()) { try { GameObject instancerGo = primMap[pathAndSample.path]; // Now build the point instances. InstanceImporter.BuildPointInstances(scene, primMap, pathAndSample.path, pathAndSample.sample, instancerGo, importOptions); } catch (System.Exception ex) { Debug.LogError("Error processing point instancer <" + pathAndSample.path + ">: " + ex.Message); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); } // // Apply root transform corrections. // Profiler.BeginSample("USD: Build Root Transforms"); if (!composingSubtree) { if (!root) { // There is no single root, // Apply root transform corrections to all imported root prims. foreach (KeyValuePair <pxr.SdfPath, GameObject> kvp in primMap) { if (kvp.Key.IsRootPrimPath() && kvp.Value != null) { // The root object at which the USD scene will be reconstructed. // It may need a Z-up to Y-up conversion and a right- to left-handed change of basis. XformImporter.BuildSceneRoot(scene, kvp.Value.transform, importOptions); } } } else { // There is only one root, apply a single transform correction. XformImporter.BuildSceneRoot(scene, root.transform, importOptions); } } Profiler.EndSample(); Profiler.BeginSample("USD: Post Process Components"); foreach (var processor in root.GetComponents <IImportPostProcessComponents>()) { try { processor.PostProcessComponents(primMap, importOptions); } catch (System.Exception ex) { Debug.LogException(ex); } } Profiler.EndSample(); }
// Recreate hierarchy. void AssignParent(pxr.SdfPath path, GameObject go) { m_primMap.Add(path, go); go.transform.SetParent(m_primMap[path.GetParentPath()].transform, worldPositionStays: false); }