/// <summary> /// Internal helper for reading data from USD. /// </summary> /// <param name="attrName">The USD attribute name.</param> /// <param name="csType">The C# type.</param> /// <param name="csValue">The C# value to populate.</param> /// <param name="usdTime">The time at which to sample key frames.</param> /// <param name="prim">The USD prim from which to read data.</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="accessMap">A map of members to include when reading.</param> /// <param name="mayVary">When not null, is populated with variability.</param> /// <returns>True on success.</returns> /// <remarks> /// Note that "success" in the return value does not indicate data was read, rather it /// indicates that no unexpected states were encountered. E.g. calling ReadAttr on a field /// with no value stored in USD will not return false, since that is not considered a failure /// state. /// </remarks> bool ReadAttr(string attrName, Type csType, ref object csValue, pxr.UsdTimeCode usdTime, pxr.UsdPrim prim, MemberInfo memberInfo, HashSet <MemberInfo> accessMap, ref bool?mayVary, string usdNamespace) { bool isNewPrimvar = csValue != null && csType.IsGenericType && csType.GetGenericTypeDefinition() == typeof(Primvar <>); bool isPrimvar = Reflect.IsPrimvar(memberInfo) || isNewPrimvar; string ns = IntrinsicTypeConverter.JoinNamespace(usdNamespace, Reflect.GetNamespace(memberInfo)); // ----------------------------------------- // // Dictionaries, read, early exit, recurse. // ----------------------------------------- // // 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)) { Type genericTypeDef = csType.GetGenericArguments()[1].IsGenericType ? csType.GetGenericArguments()[1].GetGenericTypeDefinition() : null; isNewPrimvar = genericTypeDef == typeof(Primvar <>); bool isRelationship = csType.GetGenericArguments()[1] == typeof(Relationship); bool isConnection = genericTypeDef == typeof(Connectable <>); // String dictionaries are unrolled directly into the object. // So the namespace is either the incoming namespace or empty, meaning each string value in // the dictionary becomes an attribute on the prim. // Ensure there is always a namespace immediately around this member. if (string.IsNullOrEmpty(Reflect.GetNamespace(memberInfo))) { ns = IntrinsicTypeConverter.JoinNamespace(ns, attrName); usdNamespace = IntrinsicTypeConverter.JoinNamespace(usdNamespace, attrName); } // Unfortunately, the primvars prefixing logic must be replicated here so we can discover // the dictionary member from USD. if (isPrimvar || isNewPrimvar) { ns = IntrinsicTypeConverter.JoinNamespace("primvars", ns); } var dict = csValue as System.Collections.IDictionary; ConstructorInfo ctor = (isNewPrimvar || isConnection || isRelationship) ? csType.GetGenericArguments()[1].GetConstructor(new Type[0]) : null; dict.Clear(); foreach (var prop in prim.GetAuthoredPropertiesInNamespace(ns)) { object value = null; if (ctor != null) { value = ctor.Invoke(new object[0]); } // The recursive call will also discover that this is a primvar and any associated namespace. if (ReadAttr(prop.GetBaseName(), csType.GetGenericArguments()[1], ref value, usdTime, prim, memberInfo, accessMap, ref mayVary, usdNamespace)) { if (value != null) { dict.Add(prop.GetBaseName().ToString(), value); } } } return(true); } pxr.TfToken sdfAttrName = sm_tokenCache[ns, attrName]; // ----------------------------------------- // // Relationship, read + early exit. // ----------------------------------------- // if (csType == typeof(Relationship)) { // mayVary is explicitly not set here because it has accumulation semantics: // mayVary = mayVary || false; // Which is equivalent to the no-op: // mayVary = mayVary; pxr.UsdRelationship rel = null; lock (m_stageLock) { rel = prim.GetRelationship(sm_tokenCache[sdfAttrName]); } var relationship = new Relationship(); csValue = relationship; if (rel == null || !rel.IsValid()) { return(true); } pxr.SdfPathVector paths = rel.GetTargets(); string[] result = new string[paths.Count]; for (int i = 0; i < paths.Count; i++) { result[i] = paths[i].ToString(); } relationship.targetPaths = result; return(true); } // ----------------------------------------- // // Connection Setup. // ----------------------------------------- // Connectable conn = null; if (csValue != null && csType.IsGenericType && csType.GetGenericTypeDefinition() == typeof(Connectable <>)) { conn = csValue as Connectable; if (conn != null) { // Since this is a Connectable<T>, the held value T is what's being read from USD, // so replace csValue with the held T value itself. csValue must be restored before // returning. csValue = conn.GetValue(); // Same treatment for the type. csType = conn.GetValueType(); } } // ----------------------------------------- // // Primvar Setup. // ----------------------------------------- // ValueAccessor pvAccessor = null; PrimvarBase pvBase = null; if (isNewPrimvar) { pvAccessor = csValue as ValueAccessor; pvBase = (PrimvarBase)csValue; // Since this is a Primvar<T>, the held value T is what's being read from USD, // so replace csVAlue with the held T value itself. csValue must be restored before // returning. csValue = pvAccessor.GetValue(); // Same treatment for the type. csType = pvAccessor.GetValueType(); } // ----------------------------------------- // // Lookup Type Conversion Delegate. // ----------------------------------------- // UsdTypeBinding binding; if (!sm_bindings.GetBinding(csType, out binding) && !csType.IsEnum && csType != typeof(object)) { if (string.IsNullOrEmpty(ns)) { return(false); } var sample = csValue as SampleBase; if (csValue == null) { // This could attempt to automatically constuct the needed object, then nullable objects // could be used instead to drive deserialization. return(false); } else if (sample == null) { // In this case, csValue is not null, but also cannot be converted to SampleBase. throw new ArgumentException("Type does not inherit from SampleBase: " + attrName); } Deserialize((SampleBase)csValue, prim, usdTime, accessMap, ref mayVary, usdNamespace: ns); return(true); } // ----------------------------------------- // // Prep to Read. // ----------------------------------------- // // Restore C# value to the actual property value. if (conn != null) { csValue = conn; } else if (pvAccessor != null) { csValue = pvAccessor; } // Append "primvars:" namespace to primvars. if (isPrimvar) { var joinedName = IntrinsicTypeConverter.JoinNamespace(ns, attrName); sdfAttrName = sm_tokenCache["primvars", joinedName]; } // Adjust time for variability. pxr.SdfVariability variability = Reflect.GetVariability(memberInfo); pxr.UsdTimeCode time = variability == pxr.SdfVariability.SdfVariabilityUniform ? kDefaultUsdTime : usdTime; // Allocate a temp VtValue. pxr.VtValue vtValue = (pxr.VtValue)ArrayAllocator.MallocHandle(typeof(pxr.VtValue)); try { // ----------------------------------------- // // Read Connected Paths. // ----------------------------------------- // if (conn != null) { // Connection paths cannot be animated, so mayVary is not affected. var sources = new pxr.SdfPathVector(); if (prim.GetAttribute(sdfAttrName).GetConnections(sources)) { if (sources.Count > 0) { conn.SetConnectedPath(sources[0].ToString()); } } } // ----------------------------------------- // // Read Associated Primvar Data. // ----------------------------------------- // // If this is a Primvar<T>, read the associated primvar metadata and indices. if (pvBase != null) { var attr = prim.GetAttribute(sdfAttrName); if (attr) { var pv = new pxr.UsdGeomPrimvar(attr); // ElementSize and Interpolation are not animatable, so they do not affect mayVary. pvBase.elementSize = pv.GetElementSize(); pvBase.SetInterpolationToken(pv.GetInterpolation()); // Indices are a first class attribute and may vary over time. var indices = pv.GetIndicesAttr(); if (indices) { if (accessMap != null) { if (indices.GetVariability() == pxr.SdfVariability.SdfVariabilityVarying || indices.ValueMightBeTimeVarying()) { accessMap.Add(memberInfo); mayVary |= true; } } indices.Get(vtValue, time); if (!vtValue.IsEmpty()) { var vtIntArray = pxr.UsdCs.VtValueToVtIntArray(vtValue); pvBase.indices = IntrinsicTypeConverter.FromVtArray(vtIntArray); } } } } // ----------------------------------------- // // Read the value of csValue. // ----------------------------------------- // if (Reflect.IsMetadata(memberInfo)) { vtValue = prim.GetMetadata(sdfAttrName); // Metadata cannot vary over time. } else if (Reflect.IsCustomData(memberInfo)) { vtValue = prim.GetCustomDataByKey(sdfAttrName); // Custom data is metadata, which cannot vary over time. } else if (Reflect.IsFusedDisplayColor(memberInfo)) { vtValue = pxr.UsdCs.GetFusedDisplayColor(prim, time); if (accessMap != null) { // Display color is actually two attributes, primvars:displayColor and // primvars:displayOpacity. var gprim = new pxr.UsdGeomGprim(prim); if (gprim && gprim.GetDisplayColorAttr().ValueMightBeTimeVarying()) { accessMap.Add(memberInfo); mayVary |= true; } } } else if (Reflect.IsFusedTransform(memberInfo)) { vtValue = pxr.UsdCs.GetFusedTransform(prim, time); if (accessMap != null) { // Transforms are complicated :/ var xformable = new pxr.UsdGeomXformable(prim); if (xformable) { bool dummy; var orderAttr = xformable.GetXformOpOrderAttr(); if (orderAttr) { if (orderAttr.GetVariability() == pxr.SdfVariability.SdfVariabilityVarying && orderAttr.ValueMightBeTimeVarying()) { mayVary |= true; accessMap.Add(memberInfo); } else { foreach (var op in xformable.GetOrderedXformOps(out dummy)) { var opAttr = op.GetAttr(); if (!opAttr) { continue; } if (opAttr.GetVariability() == pxr.SdfVariability.SdfVariabilityVarying && opAttr.ValueMightBeTimeVarying()) { mayVary |= true; accessMap.Add(memberInfo); break; } } // foreach } } // orderAttr } // xformable } // mayVary } else { if (accessMap != null) { var attr = prim.GetAttribute(sdfAttrName); if (attr.GetVariability() == pxr.SdfVariability.SdfVariabilityVarying && attr.ValueMightBeTimeVarying()) { accessMap.Add(memberInfo); mayVary |= true; } } if (!prim.GetAttributeValue(sdfAttrName, vtValue, time)) { // Object has no value, still considered success. return(true); } } if (vtValue.IsEmpty()) { // Object has no value, still considered success. return(true); } // ------------------------------------------ // // Infer C# type from USD when Type == Object // ------------------------------------------ // if (csType == typeof(object)) { // Blind object serialization needs special handling, since we won't know the C# type a priori. // Instead, do a reverse lookup on the SdfTypeName and let USD dictate the C# type. pxr.UsdAttribute attr = prim.GetAttribute(sdfAttrName); if (attr != null && attr.IsValid()) { // TODO: Assuming the reverse lookup is successful for the binding, the caller may be // surprised by the result, since the USD <-> C# types are not 1-to-1. For example, // a List<Vector2> may have been serialized, but Vector2[] may be read. if (!sm_bindings.GetReverseBinding(attr.GetTypeName(), out binding)) { if (string.IsNullOrEmpty(ns)) { return(false); } // TODO: readback nested object declared as object -- maybe just disable this? //Deserialize(ref csValue, prim, usdTime, usdNamespace: ns); //return true; return(false); } } else { // TODO: Allow reading metadata declared as object in C# return(false); } } // ------------------------------------------ // // Convert USD's VtValue -> Strong C# Type. // ------------------------------------------ // csValue = binding.toCsObject(vtValue); // ------------------------------------------ // // Restore csValue. // ------------------------------------------ // if (conn != null && csValue != null) { conn.SetValue(csValue); csValue = conn; } if (pvAccessor != null) { pvAccessor.SetValue(csValue); csValue = pvAccessor; } } finally { // Would prefer RAII handle, but introduces garbage. ArrayAllocator.FreeHandle(vtValue); } return(true); }
/// <summary> /// Internal helper for reading data from USD. /// </summary> /// <param name="attrName">The USD attribute name.</param> /// <param name="csType">The C# type.</param> /// <param name="csValue">The C# value to populate.</param> /// <param name="usdTime">The time at which to sample key frames.</param> /// <param name="prim">The USD prim from which to read data.</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 read, rather it /// indicates that no unexpected states were encountered. E.g. calling ReadAttr on a field /// with no value stored in USD will not return false, since that is not considered a failure /// state. /// </remarks> bool ReadAttr(string attrName, Type csType, ref object csValue, pxr.UsdTimeCode usdTime, pxr.UsdPrim prim, MemberInfo memberInfo, string usdNamespace, string srcObject = null) { bool isPrimvar = Reflect.IsPrimvar(memberInfo); string ns = IntrinsicTypeConverter.JoinNamespace(usdNamespace, Reflect.GetNamespace(memberInfo)); // If holding a dictionary, immediately recurse and write keys as attributes. if (csType == typeof(Dictionary <string, object>)) { string sourceMember; if (isPrimvar) { ns = "primvars"; sourceMember = attrName; } else { ns = IntrinsicTypeConverter.JoinNamespace(ns, attrName); sourceMember = null; } var dict = csValue as Dictionary <string, object>; foreach (var prop in prim.GetAuthoredPropertiesInNamespace(ns)) { object value = null; if (!string.IsNullOrEmpty(sourceMember)) { pxr.VtValue valSrcMember = prop.GetCustomDataByKey(sm_tokenCache["sourceMember"]); if (valSrcMember.IsEmpty() || sourceMember != (string)valSrcMember) { continue; } } if (isPrimvar) { // The recursive call will also discover that this is a primvar. ns = ""; } if (ReadAttr(prop.GetBaseName(), typeof(Object), ref value, usdTime, prim, memberInfo, ns, srcObject)) { if (value != null) { dict.Add(prop.GetBaseName(), value); } } } return(true); } pxr.TfToken sdfAttrName = sm_tokenCache[ns, attrName]; if (csType == typeof(Relationship)) { // // Read Relationship // pxr.UsdRelationship rel = null; lock (m_stageLock) { rel = prim.GetRelationship(sm_tokenCache[sdfAttrName]); } var relationship = new Relationship(); csValue = relationship; if (rel == null || !rel.IsValid()) { return(true); } pxr.SdfPathVector paths = rel.GetTargets(); string[] result = new string[paths.Count]; for (int i = 0; i < paths.Count; i++) { result[i] = paths[i].ToString(); } relationship.targetPaths = result; return(true); } UsdTypeBinding binding; Connectable conn = null; if (csValue != null && csType.IsGenericType && csType.GetGenericTypeDefinition() == typeof(Connectable <>)) { conn = csValue as Connectable; if (conn != null) { csValue = conn.GetValue(); csType = conn.GetValueType(); } } if (!sm_bindings.GetBinding(csType, out binding) && !csType.IsEnum && csType != typeof(object)) { if (string.IsNullOrEmpty(ns)) { return(false); } var sample = csValue as SampleBase; if (sample == null) { throw new Exception("Could not deserialize: Prim: " + prim.GetPath() + " namespace: " + ns); } Deserialize((SampleBase)csValue, prim, usdTime, usdNamespace: ns); return(true); } if (conn != null) { csValue = conn; } pxr.SdfVariability variability = Reflect.GetVariability(memberInfo); // Note that namespaced primvars are not supported, so "primvars" will replace the incoming // namespace. This will happen if a nested/namespaced object has a member declared as a // primvar. if (isPrimvar) { System.Diagnostics.Debug.Assert(string.IsNullOrEmpty(ns)); sdfAttrName = sm_tokenCache["primvars", attrName]; } pxr.UsdTimeCode time = variability == pxr.SdfVariability.SdfVariabilityUniform ? kDefaultUsdTime : usdTime; //using (var valWrapper = new PooledHandle<pxr.VtValue>(ArrayAllocator)) { pxr.VtValue vtValue = (pxr.VtValue)ArrayAllocator.MallocHandle(typeof(pxr.VtValue)); try { if (conn != null) { var sources = new pxr.SdfPathVector(); if (prim.GetAttribute(sdfAttrName).GetConnections(sources)) { if (sources.Count > 0) { conn.SetConnectedPath(sources[0].ToString()); } } } if (Reflect.IsCustomData(memberInfo)) { vtValue = prim.GetCustomDataByKey(sdfAttrName); } else if (Reflect.IsFusedDisplayColor(memberInfo)) { vtValue = pxr.UsdCs.GetFusedDisplayColor(prim, time); } else if (Reflect.IsFusedTransform(memberInfo)) { vtValue = pxr.UsdCs.GetFusedTransform(prim, time); } else { if (!prim.GetAttributeValue(sdfAttrName, vtValue, time)) { // Object has no value, still considered success. return(true); } } if (vtValue.IsEmpty()) { // Object has no value, still considered success. return(true); } if (csType == typeof(object)) { // Blind object serialization needs special handling, since we won't know the C# type a priori. // Instead, do a reverse lookup on the SdfTypeName and let USD dictate the C# type. pxr.UsdAttribute attr = prim.GetAttribute(sdfAttrName); if (attr != null && attr.IsValid()) { // TODO: Assuming the reverse lookup is successful for the binding, the caller may be // surprised by the result, since the USD <-> C# types are not 1-to-1. For example, // a List<Vector2> may have been serialized, but Vector2[] may be read. if (!sm_bindings.GetReverseBinding(attr.GetTypeName(), out binding)) { if (string.IsNullOrEmpty(ns)) { return(false); } // TODO: readback nested object declared as object -- maybe just disable this? //Deserialize(ref csValue, prim, usdTime, usdNamespace: ns); //return true; return(false); } } else { // TODO: Allow reading metadata declared as object in C# return(false); } } csValue = binding.toCsObject(vtValue); if (conn != null && csValue != null) { conn.SetValue(csValue); csValue = conn; } } finally { // Would prefer RAII handle, but introduces garbage. ArrayAllocator.FreeHandle(vtValue); } // Need to deal with this //if (srcObject != null) { // attr.SetCustomDataByKey(m_tokenCache["sourceMember"], srcObject); //} return(true); }