public static void ExportSkeleton(ObjectContext objContext, ExportContext exportContext) { var scene = exportContext.scene; var sample = (SkeletonSample)objContext.sample; var boneNames = exportContext.skelSortedMap[objContext.gameObject.transform]; sample.joints = new string[boneNames.Count]; sample.bindTransforms = new Matrix4x4[boneNames.Count]; sample.restTransforms = new Matrix4x4[boneNames.Count]; string rootPath = UnityTypeConverter.GetPath(objContext.gameObject.transform); sample.transform = XformExporter.GetLocalTransformMatrix( objContext.gameObject.transform, scene.UpAxis == Scene.UpAxes.Z, new pxr.SdfPath(rootPath).IsRootPrimPath(), exportContext.basisTransform); int i = 0; foreach (string bonePath in boneNames) { if (string.IsNullOrEmpty(bonePath)) { sample.joints[i] = ""; i++; continue; } var bone = exportContext.pathToBone[bonePath]; if (bonePath == rootPath) { sample.joints[i] = "/"; } else { sample.joints[i] = bonePath.Replace(rootPath + "/", ""); } // TODO: When the bone bind transform contains the geomBindTransform from USD import, it // will be mixed into each bone. This transform should be saved in some way and removed // when exported as a skeleton. sample.bindTransforms[i] = exportContext.bindPoses[bone].inverse; sample.restTransforms[i] = XformExporter.GetLocalTransformMatrix( bone, false, false, exportContext.basisTransform); if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { sample.bindTransforms[i] = UnityTypeConverter.ChangeBasis(sample.bindTransforms[i]); // The restTransforms will get a change of basis from GetLocalTransformMatrix(). } i++; } scene.Write(objContext.path, sample); // Stop Skeleton from rendering bones in usdview by default. var im = new pxr.UsdGeomImageable(scene.GetPrimAtPath(objContext.path)); im.CreatePurposeAttr().Set(pxr.UsdGeomTokens.guide); }
/// <summary> /// Serializes an arbitrary object descending from SampleBase from C# to USD. /// </summary> /// <typeparam name="T">Any type which inherits from SampleBase</typeparam> /// <param name="t">The object/data to be serialized.</param> /// <param name="prim">The UsdPrim to which the object should be written.</param> /// <param name="usdTime">The tiem at which key frames should be created.</param> /// <param name="usdNamespace">The USD namespace (if any) of the object.</param> public void Serialize <T>(T t, pxr.UsdPrim prim, pxr.UsdTimeCode usdTime, string usdNamespace = null) { PropertyInfo[] properties = Reflect.GetCachedProperties(t.GetType()); FieldInfo[] fields = Reflect.GetCachedFields(t.GetType()); var imgble = new pxr.UsdGeomImageable(prim); for (int i = 0; i < properties.Length; i++) { PropertyInfo csProp = properties[i]; Type csType = csProp.PropertyType; if (csType == typeof(object)) { if (Reflect.IsCustomData(csProp) || Reflect.IsMetadata(csProp)) { throw new ArgumentException("Writing metadata/customdata with type of object is not currently allowed"); } object o = csProp.GetValue(t, index: null); if (o != null) { csType = o.GetType(); } } if (!WriteAttr(csProp.Name, csType, csProp.GetValue(t, index:null), usdTime, prim, imgble, csProp, usdNamespace)) { // TODO: add options to dictate behavior here } } for (int i = 0; i < fields.Length; i++) { FieldInfo csField = fields[i]; Type csType = csField.FieldType; if (csType == typeof(object)) { if (Reflect.IsCustomData(csField) || Reflect.IsMetadata(csField)) { throw new ArgumentException("Writing metadata/customdata with type of object is not currently allowed"); } object o = csField.GetValue(t); if (o != null) { csType = o.GetType(); } } if (!WriteAttr(csField.Name, csType, csField.GetValue(t), usdTime, prim, imgble, csField, usdNamespace)) { // TODO: add options to dictate behavior here } } }
private static void ExportImpl(GameObject root, ExportContext context) { var scene = context.scene; bool skipInactive = context.activePolicy == ActiveExportPolicy.DoNotExport; if (context.exportMaterials) { // TODO: should account for skipped objects and also skip their materials. UnityEngine.Profiling.Profiler.BeginSample("USD: Export Materials"); foreach (var kvp in context.matMap) { Material mat = kvp.Key; string usdPath = kvp.Value; if (!mat || usdPath == null) { continue; } try { Unity.Formats.USD.MaterialExporter.ExportMaterial(scene, kvp.Key, kvp.Value); } catch (Exception ex) { Debug.LogException(new Exception("Error exporting material: " + kvp.Value, ex)); } } UnityEngine.Profiling.Profiler.EndSample(); } UnityEngine.Profiling.Profiler.BeginSample("USD: Process Export Plans"); foreach (var kvp in context.plans) { GameObject go = kvp.Key; ExportPlan exportPlan = kvp.Value; if (!go || exportPlan == null) { continue; } if (go != root && !go.transform.IsChildOf(root.transform)) { continue; } if (skipInactive && go.activeInHierarchy == false) { continue; } foreach (Exporter exporter in exportPlan.exporters) { string path = exporter.path; SampleBase sample = exporter.sample; var objCtx = new ObjectContext { gameObject = go, path = path, sample = sample, additionalData = exporter.data }; try { exporter.exportFunc(objCtx, context); } catch (Exception ex) { Debug.LogException(new Exception("Error exporting: " + path, ex)); continue; } UnityEngine.Profiling.Profiler.BeginSample("USD: Process Visibility"); try { if (!go.gameObject.activeSelf) { switch (context.activePolicy) { case ActiveExportPolicy.Ignore: // Nothing to see here. break; case ActiveExportPolicy.ExportAsVisibility: // Make the prim invisible. var im = new pxr.UsdGeomImageable(scene.GetPrimAtPath(path)); if (im) { im.CreateVisibilityAttr().Set(pxr.UsdGeomTokens.invisible); } break; case ActiveExportPolicy.ExportAsActive: // TODO: this may actually cause errors because exported prims will not exist in // the USD scene graph. Right now, that's too much responsibility on the caller, // because the error messages will be mysterious. // Make the prim inactive. scene.GetPrimAtPath(path).SetActive(false); break; } } } catch (Exception ex) { Debug.LogException(new Exception("Error setting visibility: " + path, ex)); continue; } UnityEngine.Profiling.Profiler.EndSample(); } // foreach exporter } // foreach plan UnityEngine.Profiling.Profiler.EndSample(); }
/// <summary> /// Internal helper for serializing data to USD. /// </summary> /// <param name="attrName">The USD attribute name.</param> /// <param name="csType">The C# type.</param> /// <param name="csValue">The C# value.</param> /// <param name="usdTime">The time at which to sample key frames.</param> /// <param name="prim">The USD prim at which to write values.</param> /// <param name="imgble">The UsdGeomImagable attrbiute, used when writing PrimVars.</param> /// <param name="memberInfo">The field/property providing serialization metadata.</param> /// <param name="usdNamespace">The optional USD namespace at which values live.</param> /// <param name="srcObject">The source object name, used when remapping names.</param> /// <returns>True on success.</returns> /// <remarks> /// Note that "success" in the return value does not indicate data was written, rather it /// indicates that no unexpected states were encountered. E.g. calling WriteAttr on a field /// marked as [NotSerialized] does not cause this method to return false, since non-serialized /// fields are an expected state this function may encounter. /// </remarks> bool WriteAttr(string attrName, Type csType, object csValue, pxr.UsdTimeCode usdTime, pxr.UsdPrim prim, pxr.UsdGeomImageable imgble, MemberInfo memberInfo, string usdNamespace, string srcObject = null) { if (Reflect.IsNonSerialized(memberInfo)) { Console.WriteLine("Non serialized"); return(true); } // If serializing a Primvar<T>, extract the held value and save it in csValue, allowing the // all downstream logic to act as if it's operating on the held value itself. PrimvarBase pvBase = null; if (csType.IsGenericType && csType.GetGenericTypeDefinition() == typeof(Primvar <>)) { if (csValue == null) { // Object not written, still considered success. return(true); } pvBase = (PrimvarBase)csValue; csValue = (csValue as ValueAccessor).GetValue(); if (csValue == null) { // Object not written, still considered success. return(true); } csType = csValue.GetType(); } bool isCustomData = Reflect.IsCustomData(memberInfo); bool isMetaData = Reflect.IsMetadata(memberInfo); bool isPrimvar = Reflect.IsPrimvar(memberInfo); bool isNewPrimvar = pvBase != null; int primvarElementSize = Reflect.GetPrimvarElementSize(memberInfo); string ns = IntrinsicTypeConverter.JoinNamespace(usdNamespace, Reflect.GetNamespace(memberInfo)); // If holding a dictionary, immediately recurse and write keys as attributes. if (csValue != null && csType.IsGenericType && csType.GetGenericTypeDefinition() == typeof(Dictionary <,>) && csType.GetGenericArguments()[0] == typeof(string)) { isNewPrimvar = csType.GetGenericArguments()[1].IsGenericType && csType.GetGenericArguments()[1].GetGenericTypeDefinition() == typeof(Primvar <>); // Ensure the immediate dictionary member is always namespaced. if (string.IsNullOrEmpty(Reflect.GetNamespace(memberInfo))) { usdNamespace = IntrinsicTypeConverter.JoinNamespace(usdNamespace, attrName); } var dict = csValue as System.Collections.IDictionary; foreach (System.Collections.DictionaryEntry kvp in dict) { object value = kvp.Value; WriteAttr((string)kvp.Key, value.GetType(), value, usdTime, prim, imgble, memberInfo, usdNamespace, srcObject: attrName); } return(true); } pxr.TfToken sdfAttrName = sm_tokenCache[attrName]; if (csType == typeof(Relationship) && csValue != null) { string[] targetStrings = ((Relationship)csValue).targetPaths; if (targetStrings != null) { // // Write Relationship // string[] arr = IntrinsicTypeConverter.JoinNamespace(ns, sdfAttrName).Split(':'); pxr.StdStringVector elts = new pxr.StdStringVector(arr.Length); foreach (var s in arr) { elts.Add(s); } pxr.UsdRelationship rel = null; lock (m_stageLock) { rel = prim.CreateRelationship(elts, custom: false); } if (!rel.IsValid()) { throw new ApplicationException("Failed to create relationship <" + prim.GetPath().AppendProperty( new pxr.TfToken( IntrinsicTypeConverter.JoinNamespace(ns, sdfAttrName))).ToString() + ">"); } var targets = new pxr.SdfPathVector(); foreach (var path in ((Relationship)csValue).targetPaths) { targets.Add(new pxr.SdfPath(path)); } lock (m_stageLock) { rel.SetTargets(targets); } } return(true); } // // Write Attribute // // FUTURE: When writing sparse overrides, if the csValue is null exit here and avoid // defining the target attribute. However, sparse authoring is not yet supported. UsdTypeBinding binding; // Extract the value and type from the connectable. var conn = csValue as Connectable; if (conn != null) { csType = conn.GetValueType(); csValue = conn.GetValue(); } // Get the binding for the value about to be serialized. if (!sm_bindings.GetBinding(csType, out binding) && !csType.IsEnum) { if (csValue == null) { return(true); } if (string.IsNullOrEmpty(ns)) { return(false); } var sample = csValue as SampleBase; if (sample == null && csValue != null) { throw new ArgumentException("Type does not inherit from SampleBase: " + attrName); } Serialize(csValue, prim, usdTime, usdNamespace: ns); return(true); } // Determine metadata for the attribtue, note that in the case of connections and primvars // these will be the attributes on the outter object, e.g. declared on the Connection<T> or // Primvar<T>. pxr.SdfVariability variability = Reflect.GetVariability(memberInfo); pxr.SdfValueTypeName sdfTypeName = binding.sdfTypeName; pxr.UsdTimeCode time = variability == pxr.SdfVariability.SdfVariabilityUniform ? pxr.UsdTimeCode.Default() : usdTime; bool custom = false; pxr.UsdAttribute attr; if (isCustomData || isMetaData) { // no-op attr = null; } else if (!isPrimvar && !isNewPrimvar) { if (string.IsNullOrEmpty(ns)) { // // Create non-namespaced attribute. // lock (m_stageLock) { attr = prim.CreateAttribute(sdfAttrName, csType.IsEnum ? SdfValueTypeNames.Token : sdfTypeName, custom, variability); } } else { // // Create namespaced attribute. // string[] arr = IntrinsicTypeConverter.JoinNamespace(ns, sdfAttrName).Split(':'); pxr.StdStringVector elts = new pxr.StdStringVector(arr.Length); foreach (var s in arr) { elts.Add(s); } lock (m_stageLock) { attr = prim.CreateAttribute(elts, sdfTypeName, custom, variability); } } } else { // // Create Primvar attribute. // lock (m_stageLock) { var fullAttrName = IntrinsicTypeConverter.JoinNamespace(ns, sdfAttrName); var primvar = imgble.CreatePrimvar(new pxr.TfToken(fullAttrName), sdfTypeName, VertexDataAttribute.Interpolation); if (isNewPrimvar) { primvar.SetElementSize(pvBase.elementSize); if (pvBase.indices != null) { var vtIndices = IntrinsicTypeConverter.ToVtArray(pvBase.indices); primvar.SetIndices(vtIndices, time); } primvar.SetInterpolation(pvBase.GetInterpolationToken()); } else { primvar.SetElementSize(primvarElementSize); } attr = primvar.GetAttr(); } } if (attr != null && conn != null && conn.GetConnectedPath() != null) { // TODO: Pool temp vector, possibly add a single item overload for SetConnections. var paths = new pxr.SdfPathVector(); var connPath = conn.GetConnectedPath(); if (connPath != string.Empty) { paths.Add(new pxr.SdfPath(conn.GetConnectedPath())); } attr.SetConnections(paths); } // This may happen when a connection is present, but has a null default value. // Because the connection is applied just before this point, this is the earliest possible // exit point. if (csValue == null) { return(true); } pxr.VtValue vtValue = binding.toVtValue(csValue); lock (m_stageLock) { if (isMetaData) { prim.SetMetadata(sdfAttrName, vtValue); } else if (isCustomData) { prim.SetCustomDataByKey(sdfAttrName, vtValue); } else if (Reflect.IsFusedDisplayColor(memberInfo)) { pxr.UsdCs.SetFusedDisplayColor(prim, vtValue, time); } else { attr.Set(vtValue, time); } } if (!isCustomData && srcObject != null) { lock (m_stageLock) { attr.SetCustomDataByKey(sm_tokenCache["sourceMember"], srcObject); } } return(true); }
/// <summary> /// Internal helper for serializing data to USD. /// </summary> /// <param name="attrName">The USD attribute name.</param> /// <param name="csType">The C# type.</param> /// <param name="csValue">The C# value.</param> /// <param name="usdTime">The time at which to sample key frames.</param> /// <param name="prim">The USD prim at which to write values.</param> /// <param name="imgble">The UsdGeomImagable attrbiute, used when writing PrimVars.</param> /// <param name="memberInfo">The field/property providing serialization metadata.</param> /// <param name="usdNamespace">The optional USD namespace at which values live.</param> /// <param name="srcObject">The source object name, used when remapping names.</param> /// <returns>True on success.</returns> /// <remarks> /// Note that "success" in the return value does not indicate data was written, rather it /// indicates that no unexpected states were encountered. E.g. calling WriteAttr on a field /// marked as [NotSerialized] does not cause this method to return false, since non-serialized /// fields are an expected state this function may encounter. /// </remarks> bool WriteAttr(string attrName, Type csType, object csValue, pxr.UsdTimeCode usdTime, pxr.UsdPrim prim, pxr.UsdGeomImageable imgble, MemberInfo memberInfo, string usdNamespace, string srcObject = null) { if (Reflect.IsNonSerialized(memberInfo)) { return(true); } // If holding a dictionary, immediately recurse and write keys as attributes. if (csType == typeof(Dictionary <string, object>)) { Dictionary <string, object> dict = csValue as Dictionary <string, object>; foreach (var kvp in dict) { object value = kvp.Value; WriteAttr(kvp.Key, value.GetType(), value, usdTime, prim, imgble, memberInfo, usdNamespace, srcObject: attrName); } return(true); } string ns = IntrinsicTypeConverter.JoinNamespace(usdNamespace, Reflect.GetNamespace(memberInfo)); pxr.TfToken sdfAttrName = sm_tokenCache[attrName]; if (csType == typeof(Relationship) && csValue != null) { string[] targetStrings = ((Relationship)csValue).targetPaths; if (targetStrings != null) { // // Write Relationship // string[] arr = IntrinsicTypeConverter.JoinNamespace(ns, sdfAttrName).Split(':'); pxr.StdStringVector elts = new pxr.StdStringVector(arr.Length); foreach (var s in arr) { elts.Add(s); } pxr.UsdRelationship rel = null; lock (m_stageLock) { rel = prim.CreateRelationship(elts, custom: false); } if (!rel.IsValid()) { throw new ApplicationException("Failed to create relationship <" + prim.GetPath().AppendProperty( new pxr.TfToken( IntrinsicTypeConverter.JoinNamespace(ns, sdfAttrName))).ToString() + ">"); } var targets = new pxr.SdfPathVector(); foreach (var path in ((Relationship)csValue).targetPaths) { targets.Add(new pxr.SdfPath(path)); } lock (m_stageLock) { rel.SetTargets(targets); } } return(true); } // // Write Attribute // // Object not written, still considered success. if (csValue == null) { return(true); } bool isCustomData = Reflect.IsCustomData(memberInfo); bool isPrimvar = Reflect.IsPrimvar(memberInfo); UsdTypeBinding binding; var conn = csValue as Connectable; if (conn != null) { csType = conn.GetValue().GetType(); csValue = conn.GetValue(); } if (!sm_bindings.GetBinding(csType, out binding) && !csType.IsEnum) { if (string.IsNullOrEmpty(ns)) { return(false); } Serialize(csValue, prim, usdTime, usdNamespace: ns); return(true); } pxr.SdfVariability variability = Reflect.GetVariability(memberInfo); pxr.SdfValueTypeName sdfTypeName = binding.sdfTypeName; pxr.UsdTimeCode time = variability == pxr.SdfVariability.SdfVariabilityUniform ? pxr.UsdTimeCode.Default() : usdTime; bool custom = false; pxr.UsdAttribute attr; if (isCustomData) { // no-op attr = null; } else if (!isPrimvar) { if (string.IsNullOrEmpty(ns)) { lock (m_stageLock) { attr = prim.CreateAttribute(sdfAttrName, csType.IsEnum ? SdfValueTypeNames.Token : sdfTypeName, custom, variability); } } else { string[] arr = IntrinsicTypeConverter.JoinNamespace(ns, sdfAttrName).Split(':'); pxr.StdStringVector elts = new pxr.StdStringVector(arr.Length); foreach (var s in arr) { elts.Add(s); } lock (m_stageLock) { attr = prim.CreateAttribute(elts, sdfTypeName, custom, variability); } } } else { // Primvars do not support additional namespaces. lock (m_stageLock) { attr = imgble.CreatePrimvar(sdfAttrName, sdfTypeName, VertexDataAttribute.Interpolation).GetAttr(); } } if (attr != null && conn != null && conn.GetConnectedPath() != null) { // TODO: Pool temp vector, possibly add a single item overload for SetConnections. var paths = new pxr.SdfPathVector(); var connPath = conn.GetConnectedPath(); if (connPath != string.Empty) { paths.Add(new pxr.SdfPath(conn.GetConnectedPath())); } attr.SetConnections(paths); } pxr.VtValue vtValue = binding.toVtValue(csValue); lock (m_stageLock) { if (isCustomData) { prim.SetCustomDataByKey(sdfAttrName, vtValue); } else if (Reflect.IsFusedDisplayColor(memberInfo)) { pxr.UsdCs.SetFusedDisplayColor(prim, vtValue, time); } else { attr.Set(vtValue, time); } } if (!isCustomData && srcObject != null) { lock (m_stageLock) { attr.SetCustomDataByKey(sm_tokenCache["sourceMember"], srcObject); } } return(true); }