// ----------------------------------------------------------------------------------------- //
        // string[], List<string> <--> TokenArray
        // ----------------------------------------------------------------------------------------- //
        static public VtTokenArray ToVtArray(string[] input)
        {
            var output = new VtTokenArray((uint)input.Length);

            // PERFORMANCE: this is super inefficient.
            for (int i = 0; i < input.Length; i++)
            {
                output[i] = new pxr.TfToken(input[i]);
            }
            return(output);
        }
Example #2
0
        /// <summary>
        /// Retuns a dictionary of paths and the times at which each path has a keyframe for the given
        /// attribute. Only paths rooted under the given rootPath are considered.
        /// </summary>
        public Dictionary <string, double[]> ComputeKeyFrames(string rootPath, string attribute)
        {
            var keys = new Dictionary <string, double[]>();
            var prim = GetUsdPrim(GetSdfPath(rootPath));

            if (!prim)
            {
                throw new ArgumentException("rootPath does not exist");
            }
            var sdfRootPath = GetSdfPath(rootPath);
            var tfAttrName  = new pxr.TfToken(attribute);

            foreach (var child in Stage.GetAllPrims())
            {
                if (child.GetPath() == SdfPath.AbsoluteRootPath())
                {
                    Console.WriteLine("Was abs: {0}", child.GetPath());

                    continue;
                }
                if (child.GetTypeName() != "Mesh")
                {
                    continue;
                }
                Console.WriteLine(child.GetPath());
                if (!child.GetPath().HasPrefix(sdfRootPath))
                {
                    continue;
                }

                var stdDoubleVector = child.GetAttribute(tfAttrName).GetTimeSamples();
                if (stdDoubleVector.Count == 0)
                {
                    continue;
                }

                double[] times = new double[stdDoubleVector.Count];
                stdDoubleVector.CopyTo(times);
                keys.Add(child.GetPath(), times);
            }

            return(keys);
        }
Example #3
0
        /// <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 <>); // This is true for Primvar type only
            bool   isPrimvar = Reflect.IsPrimvar(memberInfo) || isNewPrimvar;            // This is true for VertexData + Primvar type...
            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)
                {
                    UsdAttribute attr = null;
                    if (Reflect.IsFusedDisplayColor(memberInfo))
                    {
                        var gprim = new pxr.UsdGeomGprim(prim);
                        if (gprim)
                        {
                            attr = gprim.GetDisplayColorAttr();
                        }
                    }
                    else
                    {
                        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());

                        // Primvars can be indexed and 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);
        }
Example #4
0
        /// <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
                {
                    // use the sparse attribute value writer, to skip redundant time samples
                    m_sparseValueWriter.SetAttribute(attr, vtValue, time);
                }
            }

            if (!isCustomData && srcObject != null)
            {
                lock (m_stageLock) {
                    attr.SetCustomDataByKey(sm_tokenCache["sourceMember"], srcObject);
                }
            }
            return(true);
        }
        public static void BuildSkinnedMesh(string meshPath,
                                            string skelPath,
                                            SkeletonSample skeleton,
                                            UsdSkelSkinningQuery skinningQuery,
                                            GameObject go,
                                            PrimMap primMap,
                                            SceneImportOptions options)
        {
            // The mesh renderer must already exist, since hte mesh also must already exist.
            var smr = go.GetComponent <SkinnedMeshRenderer>();

            if (!smr)
            {
                throw new Exception(
                          "Error importing "
                          + meshPath
                          + " SkinnnedMeshRenderer not present on GameObject"
                          );
            }

            // Get and validate the joint weights and indices informations.
            UsdGeomPrimvar jointWeights = skinningQuery.GetJointWeightsPrimvar();
            UsdGeomPrimvar jointIndices = skinningQuery.GetJointIndicesPrimvar();

            if (!jointWeights.IsDefined() || !jointIndices.IsDefined())
            {
                throw new Exception("Joints information (indices and/or weights) are missing for: " + meshPath);
            }

            // TODO: Both indices and weights attributes can be animated. It's not handled yet.
            // TODO: Having something that convert a UsdGeomPrimvar into a PrimvarSample could help simplify this code.
            int[] indices            = IntrinsicTypeConverter.FromVtArray((VtIntArray)jointIndices.GetAttr().Get());
            int   indicesElementSize = jointIndices.GetElementSize();

            pxr.TfToken indicesInterpolation = jointIndices.GetInterpolation();

            if (indices.Length == 0 ||
                indicesElementSize == 0 ||
                indices.Length % indicesElementSize != 0 ||
                !pxr.UsdGeomPrimvar.IsValidInterpolation(indicesInterpolation))
            {
                throw new Exception("Joint indices information are invalid or empty for: " + meshPath);
            }

            float[] weights            = IntrinsicTypeConverter.FromVtArray((VtFloatArray)jointWeights.GetAttr().Get());
            int     weightsElementSize = jointWeights.GetElementSize();

            pxr.TfToken weightsInterpolation = jointWeights.GetInterpolation();

            if (weights.Length == 0 ||
                weightsElementSize == 0 ||
                weights.Length % weightsElementSize != 0 ||
                !pxr.UsdGeomPrimvar.IsValidInterpolation(weightsInterpolation))
            {
                throw new Exception("Joints weights information are invalid or empty for: " + meshPath);
            }

            // Get and validate the local list of joints.
            VtTokenArray jointsAttr = new VtTokenArray();

            skinningQuery.GetJointOrder(jointsAttr);

            // If jointsAttr wasn't define, GetJointOrder return an empty array and FromVtArray as well.
            string[] joints = IntrinsicTypeConverter.FromVtArray(jointsAttr);

            // WARNING: Do not mutate skeleton values.
            string[] skelJoints = skeleton.joints;

            if (joints == null || joints.Length == 0)
            {
                if (skelJoints == null || skelJoints.Length == 0)
                {
                    throw new Exception("Joints array empty: " + meshPath);
                }
                else
                {
                    joints = skelJoints;
                }
            }

            var mesh = smr.sharedMesh;

            // TODO: bind transform attribute can be animated. It's not handled yet.
            Matrix4x4 geomXf = UnityTypeConverter.FromMatrix(skinningQuery.GetGeomBindTransform());

            // If the joints list is a different length than the bind transforms, then this is likely
            // a mesh using a subset of the total bones in the skeleton and the bindTransforms must be
            // reconstructed.
            var bindPoses = skeleton.bindTransforms;

            if (!JointsMatch(skeleton.joints, joints))
            {
                var boneToPose = new Dictionary <string, Matrix4x4>();
                bindPoses = new Matrix4x4[joints.Length];
                for (int i = 0; i < skelJoints.Length; i++)
                {
                    boneToPose[skelJoints[i]] = skeleton.bindTransforms[i];
                }
                for (int i = 0; i < joints.Length; i++)
                {
                    bindPoses[i] = boneToPose[joints[i]];
                }
            }

            // When geomXf is identity, we can take a shortcut and just use the exact skeleton bindPoses.
            if (!ImporterBase.ApproximatelyEqual(geomXf, Matrix4x4.identity))
            {
                // Note that the bind poses were transformed when the skeleton was imported, but the
                // geomBindTransform is per-mesh, so it must be transformed here so it is in the same space
                // as the bind pose.
                XformImporter.ImportXform(ref geomXf, options);

                // Make a copy only if we haven't already copied the bind poses earlier.
                if (bindPoses == skeleton.bindTransforms)
                {
                    var newBindPoses = new Matrix4x4[skeleton.bindTransforms.Length];
                    Array.Copy(bindPoses, newBindPoses, bindPoses.Length);
                    bindPoses = newBindPoses;
                }

                // Concatenate the geometry bind transform with the skeleton bind poses.
                for (int i = 0; i < bindPoses.Length; i++)
                {
                    // The geometry transform should be applied to the points before any other transform,
                    // hence the right hand multiply here.
                    bindPoses[i] = bindPoses[i] * geomXf;
                }
            }
            mesh.bindposes = bindPoses;

            var bones       = new Transform[joints.Length];
            var sdfSkelPath = new SdfPath(skelPath);

            for (int i = 0; i < joints.Length; i++)
            {
                var jointPath = new SdfPath(joints[i]);

                if (joints[i] == "/")
                {
                    jointPath = sdfSkelPath;
                }
                else if (jointPath.IsAbsolutePath())
                {
                    Debug.LogException(new Exception("Unexpected absolute joint path: " + jointPath));
                    jointPath = new SdfPath(joints[i].TrimStart('/'));
                    jointPath = sdfSkelPath.AppendPath(jointPath);
                }
                else
                {
                    jointPath = sdfSkelPath.AppendPath(jointPath);
                }
                var jointGo = primMap[jointPath];
                if (!jointGo)
                {
                    Debug.LogError("Error importing " + meshPath + " "
                                   + "Joint not found: " + joints[i]);
                    continue;
                }
                bones[i] = jointGo.transform;
            }
            smr.bones = bones;

            bool isConstant = weightsInterpolation.GetString() == pxr.UsdGeomTokens.constant;

            // Unity 2019 supports many-bone rigs, older versions of Unity only support four bones.
#if UNITY_2019
            var bonesPerVertex = new NativeArray <byte>(mesh.vertexCount, Allocator.Persistent);
            var boneWeights1   = new NativeArray <BoneWeight1>(mesh.vertexCount * weightsElementSize, Allocator.Persistent);
            for (int i = 0; i < mesh.vertexCount; i++)
            {
                int unityIndex = i * weightsElementSize;
                int usdIndex   = isConstant
                     ? 0
                     : unityIndex;

                bonesPerVertex[i] = (byte)weightsElementSize;

                for (int wi = 0; wi < weightsElementSize; wi++)
                {
                    var bw = boneWeights1[unityIndex + wi];
                    bw.boneIndex = indices[usdIndex + wi];
                    bw.weight    = weights[usdIndex + wi];
                    boneWeights1[unityIndex + wi] = bw;
                }
            }
            // TODO: Investigate if bone weights should be normalized before this line.
            mesh.SetBoneWeights(bonesPerVertex, boneWeights1);
            bonesPerVertex.Dispose();
            boneWeights1.Dispose();
#else
            var boneWeights = new BoneWeight[mesh.vertexCount];
            for (int i = 0; i < boneWeights.Length; i++)
            {
                // When interpolation is constant, the base usdIndex should always be zero.
                // When non-constant, the offset is the index times the number of weights per vertex.
                int usdIndex = isConstant
                     ? 0
                     : i * weightsElementSize;

                var boneWeight = boneWeights[i];

                if (usdIndex >= indices.Length)
                {
                    Debug.Log("UsdIndex out of bounds: " + usdIndex
                              + " indices.Length: " + indices.Length
                              + " boneWeights.Length: " + boneWeights.Length
                              + " mesh: " + meshPath);
                }

                boneWeight.boneIndex0 = indices[usdIndex];
                boneWeight.weight0    = weights[usdIndex];

                if (indicesElementSize >= 2)
                {
                    boneWeight.boneIndex1 = indices[usdIndex + 1];
                    boneWeight.weight1    = weights[usdIndex + 1];
                }
                if (indicesElementSize >= 3)
                {
                    boneWeight.boneIndex2 = indices[usdIndex + 2];
                    boneWeight.weight2    = weights[usdIndex + 2];
                }
                if (indicesElementSize >= 4)
                {
                    boneWeight.boneIndex3 = indices[usdIndex + 3];
                    boneWeight.weight3    = weights[usdIndex + 3];
                }

                // If weights are less than 1, Unity will not automatically renormalize.
                // If weights are greater than 1, Unity will renormalize.
                // Only normalize when less than one to make it easier to diff bone weights which were
                // round-tripped and were being normalized by Unity.
                float sum = boneWeight.weight0 + boneWeight.weight1 + boneWeight.weight2 + boneWeight.weight3;
                if (sum < 1)
                {
                    boneWeight.weight0 /= sum;
                    boneWeight.weight1 /= sum;
                    boneWeight.weight2 /= sum;
                    boneWeight.weight3 /= sum;
                }

                boneWeights[i] = boneWeight;
            }

            mesh.boneWeights = boneWeights;
#endif
        }
Example #6
0
        public static GameObject Import(
            USD.NET.Scene scene,
            GameObject rootObj,
            Dictionary <SdfPath, GameObject> objectMap,
            UpdateMask mask,
            out List <string> warnings,
            List <string> pathsToUpdate = null)
        {
            // TODO: generalize this to avoid having to dig down into USD for sparse reads.
            TfToken brushToken             = new pxr.TfToken("brush");
            TfToken faceVertexIndicesToken = new pxr.TfToken("faceVertexIndices");

            warnings = new List <string>();

            // Would be nice to find a way to kick this off automatically.
            // Redundant calls are ignored.
            if (!InitUsd.Initialize())
            {
                return(null);
            }

            // PLAN: Process any UsdStage either constructing or updating GameObjects as needed.
            // This should include analysis of the time samples to see what attributes are
            // actually varying so they are updated minimally.
            UsdPrimVector prims = null;

            if (pathsToUpdate == null)
            {
                prims = scene.Stage.GetAllPrims();
            }
            else
            {
                prims = new UsdPrimVector();
                foreach (var path in pathsToUpdate)
                {
                    prims.Add(scene.Stage.GetPrimAtPath(new pxr.SdfPath(path)));
                }
            }

            for (int p = 0; p < prims.Count; p++)
            {
                // TODO: prims[p] generates garbage.
                UsdPrim     usdPrim = prims[p];
                UsdGeomMesh usdMesh = new UsdGeomMesh(usdPrim);

                if (!usdMesh)
                {
                    continue;
                }

                ExportUsd.BrushSample sample = new ExportUsd.BrushSample();

                if (mask == UpdateMask.All)
                {
                    scene.Read(usdPrim.GetPath(), sample);
                }
                else
                {
                    // TODO: Generalize this as a reusable mechanism for sparse reads.
                    if (mask == UpdateMask.Topology)
                    {
                        sample.brush = new Guid((string)usdPrim.GetCustomDataByKey(brushToken));
                        var fv = usdPrim.GetAttribute(faceVertexIndicesToken).Get(scene.Time);
                        sample.faceVertexIndices = USD.NET.IntrinsicTypeConverter.FromVtArray((VtIntArray)fv);
                    }
                    else
                    {
                        throw new NotImplementedException();
                    }
                }

                GameObject strokeObj;
                Mesh       unityMesh;

                //
                // Construct the GameObject if needed.
                //
                if (!objectMap.TryGetValue(usdPrim.GetPath(), out strokeObj))
                {
                    // On first import, we need to pull in all the data, regardless of what was requested.
                    mask = UpdateMask.All;

                    BrushDescriptor brush = BrushCatalog.m_Instance.GetBrush(sample.brush);
                    if (brush == null)
                    {
                        Debug.LogWarningFormat("Invalid brush GUID at path: <{0}> guid: {1}",
                                               usdPrim.GetPath(), sample.brush);
                        continue;
                    }
                    strokeObj = UnityEngine.Object.Instantiate(brush.m_BrushPrefab);

                    // Register the Prim/Object mapping.
                    objectMap.Add(usdPrim.GetPath(), strokeObj);

                    // Init the game object.
                    strokeObj.transform.parent = rootObj.transform;
                    strokeObj.GetComponent <MeshRenderer>().material = brush.Material;
                    strokeObj.GetComponent <MeshFilter>().sharedMesh = new Mesh();
                    strokeObj.AddComponent <BoxCollider>();
                    unityMesh = strokeObj.GetComponent <MeshFilter>().sharedMesh;
                }
                else
                {
                    unityMesh = strokeObj.GetComponent <MeshFilter>().sharedMesh;
                }

                //
                // Points
                // Note that points must come first, before all other mesh data.
                //
                if ((mask & UpdateMask.Points) == UpdateMask.Points)
                {
                    unityMesh.vertices = sample.points;
                }

                //
                // Bounds
                //
                if ((mask & UpdateMask.Bounds) == UpdateMask.Bounds)
                {
                    var bc = strokeObj.GetComponent <BoxCollider>();

                    bc.center = sample.extent.center;
                    bc.size   = sample.extent.size;

                    unityMesh.bounds = bc.bounds;
                }

                //
                // Topology
                //
                if ((mask & UpdateMask.Topology) == UpdateMask.Topology)
                {
                    unityMesh.triangles = sample.faceVertexIndices;
                }

                //
                // Normals
                //
                if ((mask & UpdateMask.Normals) == UpdateMask.Normals)
                {
                    unityMesh.normals = sample.normals;
                }

                //
                // Color & Opacity
                //
                if ((mask & UpdateMask.Colors) == UpdateMask.Colors && sample.colors != null)
                {
                    unityMesh.colors = sample.colors;
                }

                //
                // Tangents
                //
                if ((mask & UpdateMask.Tangents) == UpdateMask.Tangents && sample.tangents != null)
                {
                    unityMesh.tangents = sample.tangents;
                }

                //
                // UVs
                //
                if ((mask & UpdateMask.UVs) == UpdateMask.UVs)
                {
                    SetUv(unityMesh, 0, sample.uv);
                    SetUv(unityMesh, 1, sample.uv2);
                    SetUv(unityMesh, 2, sample.uv3);
                    SetUv(unityMesh, 3, sample.uv4);
                }
            } // For each prim

            return(rootObj);
        }