The Building Coder

Material Asset Textures, Forge Webinar Recordings

Today is the last day before my one-week vacation, directly followed non-stop by the ISEPBIM Forge and BIM workshops at ISEP, Porto University, the RTC Revit Technology Conference Europe in Porto, and the Forge Accelerator in Munich.

So come the end of the month I will probably be in dire need of another vacation   :-)

Anyway, before leaving, here is a note about another recording of the Forge webinar series published for your convenience and pleasure, and a happy resolution of a recent material asset texture related Revit API issue:

Forge Webinar Series and BIM360 Recordings

The Forge webinar series continues.

Yesterday, Mikako Harada presented an Introduction to BIM360.

As always, API keys to try it out yourself can be obtained from developer.autodesk.com, which also hosts the BIM360 documentation.

In this context, you might also be interested in the Forge DevCon recording on BIM 360.

Here are the recordings and documentation pointers for all the topics covered so far:

For code samples on any of these, please refer to the Forge Platform samples on GitHub at Developer-Autodesk, optionally adding a filter, e.g., like this for Design.automation: ...Developer-Autodesk?query=Design.automation.

The Forge Webinar series continues during the remainder of the Autodesk App Store Forge and Fusion 360 Hackathon until the end of October.

Next is session 7, an Introduction to Fusion 360 Client API:

Quick access links:

Feel free to contact us at forgehackathon@autodesk.com at any time with any questions.

Listing Material Asset Textures and Sub-Textures

An issue concerning access to material asset sub-textures was raised in the Revit API discussion forum thread on Revit 2017 API bug report: wrong texture export and resolved by the development team:

Question: I work with Revit API and, seemingly, encountered a bug with asset content.

While exporting from .rvt project a material with the following texture characteristics:

Material texture

In other words, textured material with a checker basic procedural texture and both 2 checkers as its sub-textures.

Exploring this through Python, I saw the following situation:

Material texture asset code

This can be described as follows: the first slot (asset["checker_color1"]) was exported correctly, since the method:

  asset["checker_color1"].GetAllConnectedProperties()

returned an attached asset, but the second slot was exported incorrectly, because the corresponding method threw an exception and the checker_color2 position is empty, although, as you can see above, there also must be an asset there.

Answer: I logged the issue REVIT-99286 [API: sub-texture missing in material asset -- 12174739] with our development team for further exploration.

They reply:

I cannot reproduce this issue in Revit 2016. I added layers of checker to a material's texture and with the API extraction I can see each layer being found and readable. I do not have the customer model, nor did I experiment with the debugger syntax indicated.

I suspect it's more likely the developer has a typo in his code rather than anything else.

I created a sample model with an embedded ShowMaterialInfo macro set to read from the checker material which is saved within it.

You can scroll through the output and see the multiple layers of connected assets and their nested properties.

I hope this helps.

I extracted the development team code from their macro and added it to The Building Coder samples module CmdGetMaterials.cs in release 2017.0.130.4, cf. the diff.

By the way, this code pulls in two new namespaces that were not previously used:

using System.ComponentModel;
using Autodesk.Revit.Utility;

Here is the complete material asset texture listing code including a couple of helper class definitions:

#region List Material Asset Sub-Texture
/// <summary>
/// A description of a property consists of a name, its attributes and value
/// here AssetPropertyPropertyDescriptor is used to wrap AssetProperty 
/// to display its name and value in PropertyGrid
/// </summary>
internal class AssetPropertyPropertyDescriptor : PropertyDescriptor
{
  #region Fields
  /// <summary>
  /// A reference to an AssetProperty
  /// </summary>
  private AssetProperty m_assetProperty;

  /// <summary>
  /// The type of AssetProperty's property "Value"
  /// </summary>m
  private Type m_valueType;

  /// <summary>
  /// The value of AssetProperty's property "Value"
  /// </summary>
  private Object m_value;
  #endregion

  #region Properties
  /// <summary>
  /// Property to get internal AssetProperty
  /// </summary>
  public AssetProperty AssetProperty
  {
    get { return m_assetProperty; }
  }
  #endregion

  #region override Properties
  /// <summary>
  /// Gets a value indicating whether this property is read-only
  /// </summary>
  public override bool IsReadOnly
  {
    get
    {
      return true;
    }
  }

  /// <summary>
  /// Gets the type of the component this property is bound to. 
  /// </summary>
  public override Type ComponentType
  {
    get
    {
      return m_assetProperty.GetType();
    }
  }

  /// <summary>
  /// Gets the type of the property. 
  /// </summary>
  public override Type PropertyType
  {
    get
    {
      return m_valueType;
    }
  }
  #endregion

  /// <summary>
  /// Public class constructor
  /// </summary>
  /// <param name="assetProperty">the AssetProperty which a AssetPropertyPropertyDescriptor instance describes</param>
  public AssetPropertyPropertyDescriptor( AssetProperty assetProperty )
      : base( assetProperty.Name, new Attribute[0] )
  {
    m_assetProperty = assetProperty;
  }

  #region override methods
  /// <summary>
  /// Compares this to another object to see if they are equivalent
  /// </summary>
  /// <param name="obj">The object to compare to this AssetPropertyPropertyDescriptor. </param>
  /// <returns></returns>
  public override bool Equals( object obj )
  {
    AssetPropertyPropertyDescriptor other = obj as AssetPropertyPropertyDescriptor;
    return other != null && other.AssetProperty.Equals( m_assetProperty );
  }

  /// <summary>
  /// Returns the hash code for this object.
  /// Here override the method "Equals", so it is necessary to override GetHashCode too.
  /// </summary>
  /// <returns></returns>
  public override int GetHashCode()
  {
    return m_assetProperty.GetHashCode();
  }

  /// <summary>
  /// Resets the value for this property of the component to the default value. 
  /// </summary>
  /// <param name="component">The component with the property value that is to be reset to the default value.</param>
  public override void ResetValue( object component )
  {

  }

  /// <summary>
  /// Returns whether resetting an object changes its value. 
  /// </summary>
  /// <param name="component">The component to test for reset capability.</param>
  /// <returns>true if resetting the component changes its value; otherwise, false.</returns>
  public override bool CanResetValue( object component )
  {
    return false;
  }

  /// <summary>G
  /// Determines a value indicating whether the value of this property needs to be persisted.
  /// </summary>
  /// <param name="component">The component with the property to be examined for persistence.</param>
  /// <returns>true if the property should be persisted; otherwise, false.</returns>
  public override bool ShouldSerializeValue( object component )
  {
    return false;
  }

  /// <summary>
  /// Gets the current value of the property on a component.
  /// </summary>
  /// <param name="component">The component with the property for which to retrieve the value.</param>
  /// <returns>The value of a property for a given component.</returns>
  public override object GetValue( object component )
  {
    Tuple<TypeObject> typeAndValue = GetTypeAndValue( m_assetProperty, 0 );
    m_value = typeAndValue.Item2;
    m_valueType = typeAndValue.Item1;

    return m_value;
  }

  private static Tuple<TypeObject> GetTypeAndValue( AssetProperty assetProperty, int level )
  {
    Object theValue;
    Type valueType;
    //For each AssetProperty, it has different type and value
    //must deal with it separately
    try
    {
      if( assetProperty is AssetPropertyBoolean )
      {
        AssetPropertyBoolean property = assetProperty as AssetPropertyBoolean;
        valueType = typeofAssetPropertyBoolean );
        theValue = property.Value;
      }
      else if( assetProperty is AssetPropertyDistance )
      {
        AssetPropertyDistance property = assetProperty as AssetPropertyDistance;
        valueType = typeofAssetPropertyDistance );
        theValue = property.Value;
      }
      else if( assetProperty is AssetPropertyDouble )
      {
        AssetPropertyDouble property = assetProperty as AssetPropertyDouble;
        valueType = typeofAssetPropertyDouble );
        theValue = property.Value;
      }
      else if( assetProperty is AssetPropertyDoubleArray2d )
      {
        //Default, it is supported by PropertyGrid to display Double []
        //Try to convert DoubleArray to Double []
        AssetPropertyDoubleArray2d property = assetProperty as AssetPropertyDoubleArray2d;
        valueType = typeofAssetPropertyDoubleArray2d );
        theValue = GetSystemArrayAsString( property.Value );
      }
      else if( assetProperty is AssetPropertyDoubleArray3d )
      {
        AssetPropertyDoubleArray3d property = assetProperty as AssetPropertyDoubleArray3d;
        valueType = typeofAssetPropertyDoubleArray3d );
        theValue = GetSystemArrayAsString( property.Value );
      }
      else if( assetProperty is AssetPropertyDoubleArray4d )
      {
        AssetPropertyDoubleArray4d property = assetProperty as AssetPropertyDoubleArray4d;
        valueType = typeofAssetPropertyDoubleArray4d );
        theValue = GetSystemArrayAsString( property.Value );
      }
      else if( assetProperty is AssetPropertyDoubleMatrix44 )
      {
        AssetPropertyDoubleMatrix44 property = assetProperty as AssetPropertyDoubleMatrix44;
        valueType = typeofAssetPropertyDoubleMatrix44 );
        theValue = GetSystemArrayAsString( property.Value );
      }
      else if( assetProperty is AssetPropertyEnum )
      {
        AssetPropertyEnum property = assetProperty as AssetPropertyEnum;
        valueType = typeofAssetPropertyEnum );
        theValue = property.Value;
      }
      else if( assetProperty is AssetPropertyFloat )
      {
        AssetPropertyFloat property = assetProperty as AssetPropertyFloat;
        valueType = typeofAssetPropertyFloat );
        theValue = property.Value;
      }
      else if( assetProperty is AssetPropertyInteger )
      {
        AssetPropertyInteger property = assetProperty as AssetPropertyInteger;
        valueType = typeofAssetPropertyInteger );
        theValue = property.Value;
      }
      else if( assetProperty is AssetPropertyReference )
      {
        AssetPropertyReference property = assetProperty as AssetPropertyReference;
        valueType = typeofAssetPropertyReference );
        theValue = "REFERENCE"//property.Type;
      }
      else if( assetProperty is AssetPropertyString )
      {
        AssetPropertyString property = assetProperty as AssetPropertyString;
        valueType = typeofAssetPropertyString );
        theValue = property.Value;
      }
      else if( assetProperty is AssetPropertyTime )
      {
        AssetPropertyTime property = assetProperty as AssetPropertyTime;
        valueType = typeofAssetPropertyTime );
        theValue = property.Value;
      }
      else
      {
        valueType = typeofString );
        theValue = "Unprocessed asset type: " + assetProperty.GetType().Name;
      }

      if( assetProperty.NumberOfConnectedProperties > 0 )
      {

        String result = "";
        result = theValue.ToString();

        TaskDialog.Show( "Connected properties found", assetProperty.Name + ": " + assetProperty.NumberOfConnectedProperties );
        IList<AssetProperty> properties = assetProperty.GetAllConnectedProperties();

        foreachAssetProperty property in properties )
        {
          if( property is Asset )
          {
            // Nested?
            Asset asset = property as Asset;
            int size = asset.Size;
            forint i = 0; i < size; i++ )
            {
              AssetProperty subproperty = asset[i];
              Tuple<TypeObject> valueAndType = GetTypeAndValue( subproperty, level + 1 );
              String indent = "";
              if( level > 0 )
              {
                forint iLevel = 1; iLevel <= level; iLevel++ )
                  indent += "   ";
              }
              result += "\n " + indent + "- connected: name: " + subproperty.Name + " | type: " + valueAndType.Item1.Name +
                " | value: " + valueAndType.Item2.ToString();
            }
          }
        }

        theValue = result;
      }
    }
    catch
    {
      return null;
    }
    return new Tuple<TypeObject>( valueType, theValue );
  }

  /// <summary>
  /// Sets the value of the component to a different value.
  /// For AssetProperty, it is not allowed to set its value, so here just return.
  /// </summary>
  /// <param name="component">The component with the property value that is to be set. </param>
  /// <param name="value">The new value.</param>
  public override void SetValue( object component, object value )
  {
    return;
  }
  #endregion

  /// <summary>
  /// Convert Autodesk.Revit.DB.DoubleArray to Double [].
  /// For Double [] is supported by PropertyGrid.
  /// </summary>
  /// <param name="doubleArray">the original Autodesk.Revit.DB.DoubleArray </param>
  /// <returns>The converted Double []</returns>
  private static Double[] GetSystemArray( DoubleArray doubleArray )
  {
    double[] values = new double[doubleArray.Size];
    int index = 0;
    foreachDouble value in doubleArray )
    {
      values[index++] = value;
    }
    return values;
  }

  private static String GetSystemArrayAsString( DoubleArray doubleArray )
  {
    double[] values = GetSystemArray( doubleArray );

    String result = "";
    foreachdouble d in values )
    {
      result += d;
      result += ",";
    }

    return result;
  }
}

/// <summary>
/// supplies dynamic custom type information for an Asset while it is displayed in PropertyGrid.
/// </summary>
public class RenderAppearanceDescriptor : ICustomTypeDescriptor
{
  #region Fields
  /// <summary>
  /// Reference to Asset
  /// </summary>
  Asset m_asset;

  /// <summary>
  /// Asset's property descriptors
  /// </summary>
  PropertyDescriptorCollection m_propertyDescriptors;
  #endregion

  #region Constructors
  /// <summary>
  /// Initializes Asset object
  /// </summary>
  /// <param name="asset">an Asset object</param>
  public RenderAppearanceDescriptor( Asset asset )
  {
    m_asset = asset;
    GetAssetProperties();
  }

  #endregion

  #region Methods
  #region ICustomTypeDescriptor Members

  /// <summary>
  /// Returns a collection of custom attributes for this instance of Asset.
  /// </summary>
  /// <returns>Asset's attributes</returns>
  public AttributeCollection GetAttributes()
  {
    return TypeDescriptor.GetAttributes( m_asset, false );
  }

  /// <summary>
  /// Returns the class name of this instance of Asset.
  /// </summary>
  /// <returns>Asset's class name</returns>
  public string GetClassName()
  {
    return TypeDescriptor.GetClassName( m_asset, false );
  }

  /// <summary>
  /// Returns the name of this instance of Asset.
  /// </summary>
  /// <returns>The name of Asset</returns>
  public string GetComponentName()
  {
    return TypeDescriptor.GetComponentName( m_asset, false );
  }

  /// <summary>
  /// Returns a type converter for this instance of Asset.
  /// </summary>
  /// <returns>The converter of the Asset</returns>
  public TypeConverter GetConverter()
  {
    return TypeDescriptor.GetConverter( m_asset, false );
  }

  /// <summary>
  /// Returns the default event for this instance of Asset.
  /// </summary>
  /// <returns>An EventDescriptor that represents the default event for this object, 
  /// or null if this object does not have events.</returns>
  public EventDescriptor GetDefaultEvent()
  {
    return TypeDescriptor.GetDefaultEvent( m_asset, false );
  }

  /// <summary>
  /// Returns the default property for this instance of Asset.
  /// </summary>
  /// <returns>A PropertyDescriptor that represents the default property for this object, 
  /// or null if this object does not have properties.</returns>
  public PropertyDescriptor GetDefaultProperty()
  {
    return TypeDescriptor.GetDefaultProperty( m_asset, false );
  }

  /// <summary>
  /// Returns an editor of the specified type for this instance of Asset.
  /// </summary>
  /// <param name="editorBaseType">A Type that represents the editor for this object. </param>
  /// <returns>An Object of the specified type that is the editor for this object, 
  /// or null if the editor cannot be found.</returns>
  public object GetEditor( Type editorBaseType )
  {
    return TypeDescriptor.GetEditor( m_asset, editorBaseType, false );
  }

  /// <summary>
  /// Returns the events for this instance of Asset using the specified attribute array as a filter.
  /// </summary>
  /// <param name="attributes">An array of type Attribute that is used as a filter. </param>
  /// <returns>An EventDescriptorCollection that represents the filtered events for this Asset instance.</returns>
  public EventDescriptorCollection GetEvents( Attribute[] attributes )
  {
    return TypeDescriptor.GetEvents( m_asset, attributes, false );
  }

  /// <summary>
  /// Returns the events for this instance of Asset.
  /// </summary>
  /// <returns>An EventDescriptorCollection that represents the events for this Asset instance.</returns>
  public EventDescriptorCollection GetEvents()
  {
    return TypeDescriptor.GetEvents( m_asset, false );
  }

  /// <summary>
  /// Returns the properties for this instance of Asset using the attribute array as a filter.
  /// </summary>
  /// <param name="attributes">An array of type Attribute that is used as a filter.</param>
  /// <returns>A PropertyDescriptorCollection that 
  /// represents the filtered properties for this Asset instance.</returns>
  public PropertyDescriptorCollection GetProperties( Attribute[] attributes )
  {
    return m_propertyDescriptors;
  }

  /// <summary>
  /// Returns the properties for this instance of Asset.
  /// </summary>
  /// <returns>A PropertyDescriptorCollection that represents the properties 
  /// for this Asset instance.</returns>
  public PropertyDescriptorCollection GetProperties()
  {
    return m_propertyDescriptors;
  }

  /// <summary>
  /// Returns an object that contains the property described by the specified property descriptor.
  /// </summary>
  /// <param name="pd">A PropertyDescriptor that represents the property whose owner is to be found. </param>
  /// <returns>Asset object</returns>
  public object GetPropertyOwner( PropertyDescriptor pd )
  {
    return m_asset;
  }
  #endregion

  /// <summary>
  /// Get Asset's property descriptors
  /// </summary>
  private void GetAssetProperties()
  {
    ifnull == m_propertyDescriptors )
    {
      m_propertyDescriptors = new PropertyDescriptorCollectionnew AssetPropertyPropertyDescriptor[0] );
    }
    else
    {
      return;
    }

    //For each AssetProperty in Asset, create an AssetPropertyPropertyDescriptor.
    //It means that each AssetProperty will be a property of Asset
    forint index = 0; index < m_asset.Size; index++ )
    {
      AssetProperty assetProperty = m_asset[index];
      ifnull != assetProperty )
      {
        AssetPropertyPropertyDescriptor assetPropertyPropertyDescriptor = new AssetPropertyPropertyDescriptor( assetProperty );
        m_propertyDescriptors.Add( assetPropertyPropertyDescriptor );
      }
    }
  }
  #endregion
}

public void ShowMaterialInfo( Document doc )
{
  // Find material
  FilteredElementCollector fec = new FilteredElementCollector( doc );
  fec.OfClass( typeofMaterial ) );

  String materialName = "Checker"// "Copper";//"Prism - Glass - Textured"; // "Parking Stripe"; // "Copper";
                                   // "Carpet (1)";// "Prism - Glass - Textured";// "Parking Stripe"; // "Prism 1";// "Brick, Common" ;// "Acoustic Ceiling Tile 24 x 48";  // "Aluminum"
  Material mat = fec.Cast<Material>().First<Material>( m => m.Name == materialName );



  ElementId appearanceAssetId = mat.AppearanceAssetId;

  AppearanceAssetElement appearanceAsset = doc.GetElement( appearanceAssetId ) as AppearanceAssetElement;

  Asset renderingAsset = appearanceAsset.GetRenderingAsset();



  RenderAppearanceDescriptor rad
        = new RenderAppearanceDescriptor( renderingAsset );


  PropertyDescriptorCollection collection = rad.GetProperties();

  TaskDialog.Show( "Total properties""Properties found: " + collection.Count );
  //

  string s = ": Material Asset Properties";

  TaskDialog dlg = new TaskDialog( s );

  dlg.MainInstruction = mat.Name + s;

  s = string.Empty;

  List<PropertyDescriptor> orderableCollection = new List<PropertyDescriptor>( collection.Count );

  foreachPropertyDescriptor descr in collection )
  {
    orderableCollection.Add( descr );
  }



  foreachAssetPropertyPropertyDescriptor descr in
           orderableCollection.OrderBy<PropertyDescriptorString>( pd => pd.Name ).Cast<AssetPropertyPropertyDescriptor>() )
  {
    object value = descr.GetValue( rad );

    s += "\nname: " + descr.Name
      + " | type: " + descr.PropertyType.Name
     + " | value: " + value;
  }
  dlg.MainContent = s;
  dlg.Show();


}

public void ListAllAssets(UIApplication uiapp)
{
  AssetSet assets = uiapp.Application.get_Assets( AssetType.Appearance );

  TaskDialog dlg = new TaskDialog"Assets" );

  String assetLabel = "";

  foreachAsset asset in assets )
  {
    String libraryName = asset.LibraryName;
    AssetPropertyString uiname = asset["UIName"as AssetPropertyString;
    AssetPropertyString baseSchema = asset["BaseSchema"as AssetPropertyString;

    assetLabel += libraryName + " | " + uiname.Value + " | " + baseSchema.Value;
    assetLabel += "\n";
  }

  dlg.MainInstruction = assetLabel;

  dlg.Show();
}
#endregion // List Material Asset Sub-Texture

I hope you are glad to hear that no issues were detected and that you can make good use of this handy sample code for further explorations.

Many thanks to Scott Conover for putting it together!

I wish you a happy and fruitful week!