/// <summary>
    /// Query all attributes of a specific part and a specific owner (detail, primitive, point, vertex).
    /// </summary>
    /// <param name="session">Houdini Engine session</param>
    /// <param name="geoID">The geometry object ID</param>
    /// <param name="partID">The part ID</param>
    /// <param name="owner">The attribute owner</param>
    /// <param name="count">The number of expected attributes for this owner</param>
    public static void QueryPartAttributeByOwner(HEU_SessionBase session, HAPI_NodeId geoID,
                                                 HAPI_PartId partID, HAPI_AttributeOwner owner, int count, StringBuilder sb)
    {
        if (count == 0)
        {
            HEU_Logger.LogFormat("No attributes with owner {0}", owner);
            return;
        }

        string[] attrNames = new string[count];
        if (session.GetAttributeNames(geoID, partID, owner, ref attrNames, count))
        {
            for (int i = 0; i < attrNames.Length; ++i)
            {
                HAPI_AttributeInfo attrInfo = new HAPI_AttributeInfo();
                if (HEU_GeneralUtility.GetAttributeInfo(session, geoID, partID, attrNames[i], ref attrInfo) && attrInfo.exists)
                {
                    sb.AppendLine(string.Format("Attribute {0} has storage: {1}", attrNames[i], attrInfo.storage));

                    // Query the actual values with helper for each type
                    QueryAttributeByStorageType(session, geoID, partID, ref attrInfo, attrNames[i]);
                }
            }
        }
    }
    /// <summary>
    /// Query the parameters in the HDA, and change some values.
    /// </summary>
    /// <param name="houdiniAsset">The HEU_HoudiniAsset of the loaded asset</param>
    public static void ChangeParmsAndCook(HEU_HoudiniAsset houdiniAsset)
    {
        // Always get the latest parms after each cook
        List <HEU_ParameterData> parms = houdiniAsset.Parameters.GetParameters();

        if (parms == null || parms.Count == 0)
        {
            HEU_Logger.LogFormat("No parms found");
            return;
        }

        // --------------------------------------------------------------------
        // Example to loop over each parm, checking its type and name. Then setting value.
        StringBuilder sb = new StringBuilder();

        foreach (HEU_ParameterData parmData in parms)
        {
            sb.AppendLine(string.Format("Parm: name={0}, type={1}", parmData._labelName, parmData._parmInfo.type));

            if (parmData._parmInfo.type == HAPI_ParmType.HAPI_PARMTYPE_BUTTON)
            {
                // Display a button: parmData._intValues[0];
            }
            else if (parmData._parmInfo.type == HAPI_ParmType.HAPI_PARMTYPE_FLOAT)
            {
                // Display a float: parmData._floatValues[0];

                // You can set a float this way
                HEU_ParameterUtility.SetFloat(houdiniAsset, parmData._name, 1f);

                // Or this way (the index is 0, unless its for array of floats)
                parmData._floatValues[0] = 1;
            }
        }
        HEU_Logger.Log("Parameters: \n" + sb.ToString());

        // --------------------------------------------------------------------
        // Examples to look up a parm via name, and set it.

        // Use helper to set float parameter with name
        HEU_ParameterUtility.SetFloat(houdiniAsset, "gravity", 5f);

        // Use helper to set random color
        HEU_ParameterUtility.SetColor(houdiniAsset, "branch_vtx_color_color", Random.ColorHSV());

        // Make sure to cook after changing parms
        CookAsset(houdiniAsset);
    }
    static void LogAttr(HEU_OutputAttribute outAttr)
    {
        HEU_Logger.LogFormat("Found {0} attribute:", outAttr._name);

        if (outAttr._intValues != null)
        {
            LogArray(outAttr._name, outAttr._intValues, outAttr._tupleSize);
        }
        else if (outAttr._floatValues != null)
        {
            LogArray(outAttr._name, outAttr._floatValues, outAttr._tupleSize);
        }
        else if (outAttr._stringValues != null)
        {
            LogArray(outAttr._name, outAttr._stringValues, outAttr._tupleSize);
        }
    }
    /// <summary>
    /// Query a specific attribute on an asset, within its geometry.
    /// </summary>
    /// <param name="objName">The object name</param>
    /// <param name="geoName">The SOP geometry name</param>
    /// <param name="partID">The part ID</param>
    /// <param name="attrName">The attribute name</param>
    public static void QueryAttribute(HEU_HoudiniAsset houdiniAsset, string objName, string geoName, HAPI_PartId partID, string attrName)
    {
        // Get access to the Houdini Engine session used by this asset.
        // This gives access to call Houdini Engine APIs directly.
        HEU_SessionBase session = houdiniAsset.GetAssetSession(true);

        if (session == null || !session.IsSessionValid())
        {
            HEU_Logger.LogWarningFormat("Invalid Houdini Engine session! Try restarting session.");
            return;
        }

        // First get the object (transform) node, then the geometry container, then the part.
        // Finally, get the attribute on the part.

        HEU_ObjectNode objNode = houdiniAsset.GetObjectNodeByName(objName);

        if (objNode == null)
        {
            HEU_Logger.LogWarningFormat("Object with name {0} not found in asset {1}!", objName, houdiniAsset.AssetName);
            return;
        }

        HEU_GeoNode geoNode = objNode.GetGeoNode(geoName);

        if (geoNode == null)
        {
            HEU_Logger.LogWarningFormat("Geometry with name {0} not found in object {1} in asset {2}!", geoNode.GeoName, objName, houdiniAsset.AssetName);
        }

        HAPI_AttributeInfo attrInfo = new HAPI_AttributeInfo();

        if (!HEU_GeneralUtility.GetAttributeInfo(session, geoNode.GeoID, partID, attrName, ref attrInfo) && attrInfo.exists)
        {
            HEU_Logger.LogWarningFormat("Attribute {0} not found in asset.", attrName);
        }

        HEU_Logger.LogFormat("Found attribute {0} on geo {1}", attrName, geoName);

        // Now query the actual values on this attribute
        QueryAttributeByStorageType(session, geoNode.GeoID, partID, ref attrInfo, attrName);
    }
    /// <summary>
    /// Example to show how to use the HEU_OutputAttributeStore component to query
    /// attribute data and set it on instances.
    /// This should be used with HEUInstanceAttributesStore.hda.
    /// This function is called after HDA is cooked.
    /// </summary>
    void InstancerCallback()
    {
        // Acquire the attribute storage component (HEU_OutputAttributesStore).
        // HEU_OutputAttributesStore contains a dictionary of attribute names to attribute data (HEU_OutputAttribute).
        // HEU_OutputAttributesStore is added to the generated gameobject when an attribute with name
        // "hengine_attr_store" is created at the detail level.
        HEU_OutputAttributesStore attrStore = gameObject.GetComponent <HEU_OutputAttributesStore>();

        if (attrStore == null)
        {
            HEU_Logger.LogWarning("No HEU_OutputAttributesStore component found!");
            return;
        }

        // Query for the health attribute (HEU_OutputAttribute).
        // HEU_OutputAttribute contains the attribute info such as name, class, storage, and array of data.
        // Use the name to get HEU_OutputAttribute.
        // Can use HEU_OutputAttribute._type to figure out what the actual data type is.
        // Note that data is stored in array. The size of the array corresponds to the data type.
        // For instances, the size of the array is the point cound.
        HEU_OutputAttribute healthAttr = attrStore.GetAttribute("health");

        if (healthAttr != null)
        {
            LogAttr(healthAttr);
        }

        // Query for the vector size attribute
        HEU_OutputAttribute sizeAttr = attrStore.GetAttribute("size");

        if (sizeAttr != null)
        {
            LogAttr(sizeAttr);
        }

        // Query for the stringdata attribute
        HEU_OutputAttribute stringAttr = attrStore.GetAttribute("stringdata");

        if (stringAttr != null)
        {
            LogAttr(stringAttr);
        }

        // Example of how to map the attribute array values to instances
        // Get the generated instances as children of this gameobject.
        // Note that this will include the current parent as first element (so its number of children + 1 size)
        Transform[] childTrans  = transform.GetComponentsInChildren <Transform>();
        int         numChildren = childTrans.Length;

        // Starting at 1 to skip parent transform
        for (int i = 1; i < numChildren; ++i)
        {
            HEU_Logger.LogFormat("Instance {0}: name = {1}", i, childTrans[i].name);

            // Can use the name to match up indices
            string instanceName = "Instance" + i;
            if (childTrans[i].name.EndsWith(instanceName))
            {
                // Now apply health as scale value
                Vector3 scale = childTrans[i].localScale;

                // Health index is -1 due to child indices off by 1 because of parent
                scale.y = healthAttr._intValues[i - 1];

                childTrans[i].localScale = scale;
            }
        }
    }