Unofficial Parameters and BipChecker

I climbed the Nadelhorn last weekend, the highest point in Switzerland for me so far, and the second highest point I have ever been, actually, 4387 m. Except for airplanes, of course. Here is a picture of the panorama from the Nadehorn summit with Monte Rosa, Dom, Matterhorn, Mont Blanc, etc.

Nadelhorn summit panorama

Here are some more pictures of our tour.

Meanwhile, on the Revit API side, Victor Chekalin, or Виктор Чекалин, raised a number of pertinent issues lately. One of them is this question in the comment on parameters:

Question: In my project I find parameters on a particular element that do not appear in the Element.Parameters collection. In exploring element parameters, you wrote: "Sometimes an element will have other parameters attached to it as well, which are not listed in the 'official' collection returned by the Parameters property. To explore all parameters attached to an element, one can make use of the built-in parameter checker." And nothing more. Can you explain the meaning of these parameters? In my case I need to get the length of any element if length exists. I wrote the following code for it:

private Double? GetElementLength( Element element )
{
  var lengthParam 
    = Enum.GetValues( typeof( BuiltInParameter ) )
      .OfType<BuiltInParameter>()
      .Where( p => p.ToString().Contains( "LENGTH" ) )
      .Select( param => element.get_Parameter( param ) )
      .Where( param => param != null )
      .FirstOrDefault();
 
  if( lengthParam != null )
  {
    var lengthValue = lengthParam.AsDouble();
    return lengthValue;
  }
 
  return null;
}

At first in BuiltInParameters I find all parameters whose name contains the word "LENGTH". Then I check for each if this BuiltInParameter exists in element. If it exists, return the value of this parameter. The problem: there are sometimes no length parameters in Element.Parameters, but I can still retrieve such a parameter via Element.get_Parameter. Of course I can get the length of the element by enumerating all parameters and checking the parameter name for length, but I'm curious about these "strange invisible" parameters.

Answer: Yes, there may be lots of parameters attached to elements that do not appear in the 'official' Element.Parameters collection.

You can retrieve them using any of the standard Element.Parameter property overloads.

The Element.Parameter Property

While we are at it, here are a few observations of strangeness on the Parameter property itself:

The complete list of arguments accepted by the overloads of the Parameter property or get_Parameter method is:

The Definition and name string can be provided and used for any parameter. The BuiltInParameter enumeration value or 'parameter id' as it is called above only works for built-in parameters, and the GUID only for shared ones, so they work for mutually exclusive situations.

So if the parameter you are looking for is named "Length", you can retrieve it using that name.

I would recommend you not to do so, however, because this makes you code language dependent.

Better is to determine exactly which built-in parameter corresponds to it and use that instead.

Another issue with the approach you describe above is that for a complex element, there may be several different built-in parameters whose enumeration name contains the substring "LENGTH", and they may have completely different meanings. It is even not completely clear what you yourself mean by 'length' of an element.

Snooping and the Built-in Parameter Checker

Here is a code snippet with a loop that shows how to retrieve all parameters from an element. Actually, it may not retrieve all, but it mostly retrieves more than the official Parameters collection contains:

Array bips = Enum.GetValues(typeof(BuiltInParameter));
Parameter p;
foreach (BuiltInParameter a in bips)
{
  try
  {
    p = elem.get_Parameter(a);
  }
  catch { }
}

This kind of loop to retrieve a value for each and every built-in parameter enumeration value is also used by the RevitLookup 'Built-In Enum Snoop' in the Snoop Parameters dialogue and my own built-in parameter checker. Here are some explorations I made in the past in which I discussed the use of these tools and provided the source code for my built-in parameter checker:

Since I do find my built-in parameter checker a very handy tool I decided to simplify access to and installation of it by extracting the code from the obsolete Revit API Introduction Labs.

Besides simply displaying the list of 'unofficial' element parameter values, like the RevitLookup 'Built-In Enum Snoop' functionality, this tool provides the following additional advantages:

The ability to sort by any column makes it much easier to find the records you are interested in, since you can sort by and then locate by either built-in parameter enumeration value, user visibly name, type, string or database value, or even read-write status.

Extracting and cleaning up this code also provided a welcome opportunity to make use of the generic CanHaveTypeAssigned and GetTypeId element methods. Previously, the tool only supported displaying the type parameters for family instances. Now, this functionality should work for all elements that can have a type. The updated code of the external command Execute mainline below includes both the old family instance code and the implementation of the new generic functionality:

public Result Execute(
  ExternalCommandData commandData,
  ref string message,
  ElementSet elements )
{
  UIApplication uiapp = commandData.Application;
  UIDocument uidoc = uiapp.ActiveUIDocument;
  Document doc = uidoc.Document;
 
  // Select element:
 
  Element e 
    = Util.GetSingleSelectedElementOrPrompt( 
      uidoc );
 
  if( null == e )
  {
    return Result.Cancelled;
  }
  bool isSymbol = false;
  string family_name = string.Empty;
 
  // For a family instance, ask user whether to 
  // display instance or type parameters; in a 
  // similar manner, we could add dedicated 
  // switches for Wall --> WallType, 
  // Floor --> FloorType etc. ...
 
  if( e is FamilyInstance )
  {
    FamilyInstance inst = e as FamilyInstance;
    if( null != inst.Symbol )
    {
      string symbol_name = Util.ElementDescription( 
        inst.Symbol, true );
 
      family_name = Util.ElementDescription( 
        inst.Symbol.Family, true );
 
      string msg = string.Format( _type_prompt,
        "is a family instance" );
 
      if( !Util.QuestionMsg( msg ) )
      {
        e = inst.Symbol;
        isSymbol = true;
      }
    }
  }
  else if( e.CanHaveTypeAssigned() )
  {
    ElementId typeId = e.GetTypeId();
    if( null == typeId )
    {
      Util.InfoMsg( "Element can have a type,"
        + " but the current type is null." );
    }
    else if( ElementId.InvalidElementId == typeId )
    {
      Util.InfoMsg( "Element can have a type,"
        + " but the current type id is the"
        + " invalid element id." );
    }
    else
 
    {
      Element type = doc.get_Element( typeId );
 
      if( null == type )
      {
        Util.InfoMsg( "Element has a type,"
          + " but it cannot be accessed." );
      }
      else
      {
        string msg = string.Format( _type_prompt,
          "has an element type" );
 
        if( !Util.QuestionMsg( msg ) )
        {
          e = type;
          isSymbol = true;
        }
      }
    }
  }
 
  // Retrieve parameter data:
 
  SortableBindingList<ParameterData> data 
    = new SortableBindingList<ParameterData>();
 
  {
    WaitCursor waitCursor = new WaitCursor();
 
    Array bips = Enum.GetValues( 
      typeof( BuiltInParameter ) );
 
    int n = bips.Length;
    Parameter p;
 
    foreach( BuiltInParameter a in bips )
    {
      try
      {
        p = e.get_Parameter( a );
 
        if( null != p )
        {
          string valueString = 
            (StorageType.ElementId == p.StorageType)
              ? Util.GetParameterValue2( p, doc )
              : p.AsValueString();
 
          data.Add( new ParameterData( a, p, 
            valueString ) );
        }
      }
      catch( Exception ex )
      {
        Debug.Print( 
          "Exception retrieving built-in parameter {0}: {1}",
          a, ex );
      }
    }
  }
 
  // Display form:
 
  string description 
    = Util.ElementDescription( e, true ) 
    + ( isSymbol 
      ? " Type" 
      : " Instance" );
 
  using( BuiltInParamsCheckerForm form 
    = new BuiltInParamsCheckerForm( 
      description, data ) )
  {
    form.ShowDialog();
  }
  return Result.Succeeded;
}

For the rest of the implementation, please refer to BipChecker01.zip containing the full source code, add-in manifest, and Visual Studio solution for BipChecker version 2012.0.1.0.

Response: I understood that parameters may not appear in the Element.Parameters collection, and also how to get them via the Element.get_Parameter method. I still do not understand why Autodesk developers make some parameters hidden from user. I guess that these hidden parameters are system related and used in system methods. Is that it?

I find it difficult to know whether parameter is present in the Element.Parameters collection or not. I've found only one way to check it:

bool IsParameterInCollection( Parameter parameter )
{
  foreach( Parameter p 
    in parameter.Element.Parameters )
  {
    if( p.IsShared 
      != _parameter.IsShared )
    {
      return false;
    }
 
    if( ( p.Definition as 
        InternalDefinition ).BuiltInParameter 
      == ( _parameter.Definition as 
        InternalDefinition ).BuiltInParameter )
    {
      _parameterInElementParametersCollection = true;
      return true;
    }
  }
  return false;
}

Regarding the second question on how to get the length of any element:

I agree with you that looking for parameter whose name contains 'Length' is a bad approach.

You say 'Better is to determine exactly which built-in parameter corresponds to it and use that instead'. But how I can do it if I don't know anything about the element I am analysing? In my case it may be absolutely any element.

I've found some parameters among BuiltInParameters that contains word 'LENGTH' but named not 'length'. So, my other approach is not good either.

Now I think apply next algorithm to find length:

I looked at your useful BipChecker tool and made some little changes.

I added a ParameterGroup field to the ParameterData class, and added a different parameter form with grouping using a ListView instead of DataGridView. Now you can see which parameters are provided by the Element.Parameters collection and which are retrieved via BuiltInParameter. It seems more user friendly than in DataGridView.

I hope you like these changes.

Here is BipCheckerVc.zip containing Victor's version using the list view display.

Answer: Great, I love the addition of the new properties for the parameter group, group name, and whether or not a parameter is included in the Element.Parameters collection or not.

Since the ParameterData class is getting a bit heavy now, I moved it out into a new module of its own.

I do not like the list view all that much, because it segregates the parameters into separate groups and prevents me from sorting them all in one collection by any one of the properties. I therefore re-implemented the data grid view as well. You can switch back and forth between the two views by setting a compile time pragma and rebuilding:

#if USE_LIST_VIEW
  using( BuiltInParamsCheckerFormListView form
    = new BuiltInParamsCheckerFormListView(
      description, data ) )
#else
  using (BuiltInParamsCheckerForm form
    = new BuiltInParamsCheckerForm(
      description, data))
#endif // USE_LIST_VIEW
  {
    form.ShowDialog();
  }

I have not defined USE_LIST_VIEW in my code, and therefore the data grid view is used. If you want to use the list view instead, simply add the following as the first line of the file:

#define USE_LIST_VIEW

Determining Whether a Parameter is Contained in Element.Parameters

Yes, I see that it is indeed unexpectedly difficult to determine whether a given parameter retrieved from an element using the Parameter property is contained in the official Element.Parameters collection.

The ParameterSet returned by Element.Parameters does provide a Contains method, but it does not return the expected result.

For example, for a wall, which has a parameter named "Length" with the built-in parameter id CURVE_ELEM_LENGTH, the following code snippet will still return false:

  wall.Parameters.Contains( 
    wall.get_Parameter( 
      BuiltInParameter.CURVE_ELEM_LENGTH ) )

I used your code as a basis for implementing the following workaround:

/// <summary>
/// Return BuiltInParameter id for a given parameter,
/// assuming it is a built-in parameter.
/// </summary>
static BuiltInParameter BipOf( Parameter p )
{
  return ( p.Definition as InternalDefinition )
    .BuiltInParameter;
}
 
/// <summary>
/// Check whether two given parameters represent
/// the same parameter, i.e. shared parameters
/// have the same GUID, others the same built-in
/// parameter id.
/// </summary>
static bool IsSameParameter( Parameter p, Parameter q )
{
  return( p.IsShared == q.IsShared )
    && ( p.IsShared
      ? p.GUID.Equals( q.GUID )
      : BipOf( p ) == BipOf( q ) );
}
 
/// <summary>
/// Return true if the given element parameter 
/// retrieved by  get_parameter( BuiltInParameter ) 
/// is contained in the element Parameters collection.
/// Workaround to replace ParameterSet.Contains.
/// Why does this not work?
/// return _parameter.Element.Parameters.Contains(_parameter);
/// </summary>
bool ContainedInCollection( Parameter p, ParameterSet set )
{
  bool rc = false;
 
  foreach( Parameter q in set )
  {
    rc = IsSameParameter( p, q );
 
    if( rc )
    {
      break;
    }
  }
  return rc;
}

Of course, as Victor pointed out, in the current context, one of the parameters will always be a built-in one obtained from the iteration over the BuiltInParameter enumeration, so the situation that both parameters are shared will never arise.

Furthermore, the comparison above can be vastly simplified by using the parameters Id property instead of the GUID or the built-in parameter id.

Making use of the Id property and letting LINQ perform the list processing operations for us in a generic fashion, all three methods above can be condensed into

  bool ContainedInCollection( 
    Parameter p, 
    ParameterSet set )
  {
    return set
      .OfType<Parameter>()
      .Any( x => x.Id == p.Id );
  }

Here is BipChecker03.zip containing the source code, add-in manifest, and Visual Studio solution for the updated BipChecker version 2012.0.3.0.

Here is what it looks like now displaying the parameters of a standard wall:

BipChecker

I hope you find this as useful as I do!

P.S. One thing that is possibly still missing here is support for shared parameters. If you need that, add it, and let us know, please.