Unit Abbreviations

Today, we look at determining the units for a given parameter, and specifically providing a suitable unit abbreviation for it.

The Revit 2014 API introduced a new Unit API with a large number of enhancements to complete and simplify unit handling issues, and I have not yet explored all of them in depth. Here is the overview of it from What's New in the Revit 2014 API:

Units API

The API for Units in Revit has been expanded and changed. The methods

allow interaction with the units of a document.  The Units class provides access to data such as

The FormatOptions class provides access to data including:

LabelUtils.GetLabelFor() has been enhanced so that it can now return the user-visible name for a UnitSymbolType.

Unit Formatting and Parsing

The methods:

provide the ability to format a value into a string based on formatting options and to parse a formatted string (including units) into a value if possible.

Unit Conversion

The new class UnitUtils contains methods to convert between unit types:

In fact, probably very few people are aware of all the powerful functionality now available.

This became clear to me once again when I was prompted by this query to explore the specific aspect of retrieving or defining abbreviations for the display unit types:

Question: How can I determine the units for a given Parameter or FamilyParameter?

I tried to use the LabelUtils GetLabelFor method, passing in the parameter DisplayUnitType.

This returns the full unit name, e.g., "Millimeters" for DUT_MILLIMETERS.

I would prefer something shorter, e.g. an abbreviation like "mm".

How could I achieve that, please?

Answer: First of all, congratulations on discovering the approach you describe.

It sounds like the best the way to go to me.

The parameter definition defines the basic unit type, e.g. simple ones like length and more complex combined ones like stress. The possible unit types are listed in the UnitType enumeration.

The parameter itself specifies the unit in which this data is displayed to the user, which is controlled by the unit settings, and may be set to things like ft, m or mm for length, and kg / (m · s²), ksi or MPa for stress. The possible display unit types are listed in the DisplayUnitType enumeration.

We already looked at one unit conversion and display string formatting issue in some depth, e.g. to define the _map_parameter_type_to_unit_type mapping, the ParameterUnitConverter class and the external command CmdParameterUnitConverter included in The Building Coder samples, and more recently at another Parameter DisplayUnitType enhancement.

The LabelUtils class provides methods to return user-visible names for enumerations via the different overloads of the GetLabelFor method.

That leads to the approach you already discovered.

If you would like a method to return abbreviations for the display unit types instead of their full names, the most efficient approach is probably to implement your own abbreviation list.

Alternatively, if you are clever, you can also use the Revit API functionality to automatically generate an abbreviation for you.

I discuss those two options, implement an external command to test them, point to the download and wind up with a little gift.

How to Implement your Own Hard Coded Display Unit Type Abbreviation List

You can implement your own display unit type abbreviations right away and it is a very easy thing to do.

In fact, I went and did it for you, for the first 26 of them, at least:

  /// <summary>
  /// Hard coded abbreviations for the first 26
  /// DisplayUnitType enumeration values.
  /// </summary>
  public static string[] DisplayUnitTypeAbbreviation
    = new string[] {
      "m", // DUT_METERS = 0,
      "cm", // DUT_CENTIMETERS = 1,
      "mm", // DUT_MILLIMETERS = 2,
      "ft", // DUT_DECIMAL_FEET = 3,
      "N/A", // DUT_FEET_FRACTIONAL_INCHES = 4,
      "N/A", // DUT_FRACTIONAL_INCHES = 5,
      "in", // DUT_DECIMAL_INCHES = 6,
      "ac", // DUT_ACRES = 7,
      "ha", // DUT_HECTARES = 8,
      "N/A", // DUT_METERS_CENTIMETERS = 9,
      "y^3", // DUT_CUBIC_YARDS = 10,
      "ft^2", // DUT_SQUARE_FEET = 11,
      "m^2", // DUT_SQUARE_METERS = 12,
      "ft^3", // DUT_CUBIC_FEET = 13,
      "m^3", // DUT_CUBIC_METERS = 14,
      "deg", // DUT_DECIMAL_DEGREES = 15,
      "N/A", // DUT_DEGREES_AND_MINUTES = 16,
      "N/A", // DUT_GENERAL = 17,
      "N/A", // DUT_FIXED = 18,
      "%", // DUT_PERCENTAGE = 19,
      "in^2", // DUT_SQUARE_INCHES = 20,
      "cm^2", // DUT_SQUARE_CENTIMETERS = 21,
      "mm^2", // DUT_SQUARE_MILLIMETERS = 22,
      "in^3", // DUT_CUBIC_INCHES = 23,
      "cm^3", // DUT_CUBIC_CENTIMETERS = 24,
      "mm^3", // DUT_CUBIC_MILLIMETERS = 25,
      "l" // DUT_LITERS = 26,
    };

Note that this a hard coded array, indexed with integer numbers.

I am completely free to choose any abbreviation I like.

An integer index into the list can be obtained by casting from a DisplayUnitType enumeration value.

This array will only return the correct abbreviations as long as the underlying DisplayUnitType enumeration values correspond to the expected integer values.

Therefore, I would strongly recommend you to implement some code in your application start-up that ensures that this is the case, e.g. by calling a sequence of debug assertions like the following:

  const string _s = "unexpected display unit type "
    + "enumeration sequence";
 
  Debug.Assert( 0 == (int) DisplayUnitType.DUT_METERS, _s );
  Debug.Assert( 1 == (int) DisplayUnitType.DUT_CENTIMETERS, _s );
  Debug.Assert( 2 == (int) DisplayUnitType.DUT_MILLIMETERS, _s );

  . . .

  Debug.Assert( 26 == (int) DisplayUnitType.DUT_LITERS, _s );

Wouldn't it be nice to avoid this hard-coded list and this sequence of assertions?

Well, you can!

How to Automatically Generate Display Unit Type Abbreviations Using the Revit API

As said, the Revit API provides a lot of unit handling functionality, and some of it is still poorly known.

I first looked for ways extract the desired abbreviations from a value formatted to the desired unit by the FormatUtils.Format or UnitFormatUtils.Format methods.

They format a number with units into a string, either based on a list of input arguments or based on the units formatting settings for a given document.

If you set up a document to use millimetres and pass it in with the unit type UT_Length and the value 1.0, I would assume it will return the string "1 mm", from which you might be able to extract the desired abbreviation.

I'll leave that as an exercise to the thus inclined reader, though, because I preferred to stick with the display unit types – with the DUT_ prefix – and completely avoid the non-display unit types – with the UT_ prefix – required by these methods.

After some further digging on the Revit API help file RevitAPI.chm, I discovered the FormatOptions GetValidUnitSymbols method, which does indeed take a display unit type input argument and returns a list of valid unit symbols for it, represented by UnitSymbolType enumeration values.

Here are the first 22 UnitSymbolType enumeration values, out of a total of about 300:

  UST_NONE = 0,
  UST_M = 1,
  UST_CM = 101,
  UST_MM = 201,
  UST_LF = 301,
  UST_FOOT_SINGLE_QUOTE = 302,
  UST_INCH_DOUBLE_QUOTE = 601,
  UST_ACRES = 701,
  UST_HECTARES = 801,
  UST_CY = 1001,
  UST_SF = 1101,
  UST_FT_SUP_2 = 1102,
  UST_FT_CARET_2 = 1103,
  UST_M_SUP_2 = 1201,
  UST_M_CARET_2 = 1202,
  UST_CF = 1301,
  UST_FT_SUP_3 = 1302,
  UST_FT_CARET_3 = 1303,
  UST_M_SUP_3 = 1401,
  UST_M_CARET_3 = 1402,
  UST_DEGREE_SYMBOL = 1501,
  UST_PERCENT_SIGN = 1901,
  ...

Looking at these enumeration values, I notice that they correspond pretty closely with the hard coded abbreviation strings that I define above. Actually, all I have to do is replace the substring "_SUP_" by "^" and convert them to lower case to obtain an identical list.

Accordingly, I implemented the following helper method to fulfil that task:

  /// <summary>
  /// Convert a UnitSymbolType enumeration value
  /// to a brief human readable abbreviation string.
  /// </summary>
  public static string UnitSymbolTypeString(
    UnitSymbolType u )
  {
    string s = u.ToString();
 
    Debug.Assert( s.StartsWith( "UST_" ),
      "expected UnitSymbolType enumeration value "
      + "to begin with 'UST_'" );
 
    s = s.Substring( 4 )
      .Replace( "_SUP_", "^" )
      .ToLower();
 
    return s;
  }
 

CmdDutAbbreviation External Command to Test the Display Unit Type Abbreviations

I added a new external command CmdDutAbbreviation to The Building Coder samples to test both versions of my new display unit type abbreviation functionality.

It obviously uses read-only transaction mode, since it does not interact with the Revit database in any way whatsoever.

Omitting the list of assertions described above to verify the expected order of DisplayUnitType enumeration values, the implementation of the CmdDutAbbreviation external command mainline Execute method looks like this:

  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    DisplayUnitType n
      = DisplayUnitType.DUT_GALLONS_US;
 
    Debug.Print( "Here is a list of the first {0} "
      + "display unit types with The Building Coder "
      + "abbreviation and the valid unit symbols:\n",
      (int) n - 1 );
 
    string valid_unit_symbols;
 
    for( DisplayUnitType i = DisplayUnitType
      .DUT_METERS; i < n; ++i )
    {
      valid_unit_symbols = string.Join( ", ",
        FormatOptions.GetValidUnitSymbols( i )
          .Select<UnitSymbolType, string>(
            u => Util.UnitSymbolTypeString( u ) ) );
 
      Debug.Print( "{0,6} - {1}: {2}",
        Util.DisplayUnitTypeAbbreviation[(int)i],
        LabelUtils.GetLabelFor( i ),
        valid_unit_symbols,
        i );
    }
    return Result.Succeeded;
  }

For the first 26 display unit types, it generates the following list of abbreviations from the hard coded string array and the UnitSymbolTypeString method applied to the valid unit symbols:

     m - Meters: none, m
    cm - Centimeters: none, cm
    mm - Millimeters: none, mm
    ft - Decimal feet: none, foot_single_quote, lf
   N/A - Feet and fractional inches: none
   N/A - Fractional inches: none
    in - Decimal inches: none, inch_double_quote
    ac - Acres: none, acres
    ha - Hectares: none, hectares
   N/A - Meters and centimeters: none
   y^3 - Cubic yards: none, cy
  ft^2 - Square feet: none, sf, ft^2
   m^2 - Square meters: none, m^2
  ft^3 - Cubic feet: none, cf, ft^3
   m^3 - Cubic meters: none, m^3
   deg - Decimal degrees: none, degree_symbol
   N/A - Degrees minutes seconds: none
   N/A - General: none
   N/A - Fixed: none
     % - Percentage: none, percent_sign
  in^2 - Square inches: none, in^2
  cm^2 - Square centimeters: none, cm^2
  mm^2 - Square millimeters: none, mm^2
  in^3 - Cubic inches: none, in^3
  cm^3 - Cubic centimeters: none, cm^3
  mm^3 - Cubic millimeters: none, mm^3
     l - Liters: none, l

Funnily enough, as you can see, my hand written hard coded abbreviations correspond exactly with the ones I generate automatically from the UnitSymbolType list returned by the FormatOptions GetValidUnitSymbols method.

Download

As you already know, The Building Coder samples are on GitHub, so the most up-to-date code is available from the The Building Coder samples GitHub repository. The version discussed above is release 2014.0.105.0.

Gift

Let me close for today with the following uplifting poem by Czeslaw Milosz (1911–2004), 1980 Nobel Prize winner in literature:

Gift

A day so happy.
Fog lifted early. I worked in the garden.
Hummingbirds were stopping over the honeysuckle flowers.
There was no thing on earth I wanted to possess.
I knew no one worth my envying him.
Whatever evil I had suffered, I forgot.
To think that once I was the same man did not embarrass me.
In my body I felt no pain.
When straightening up, I saw blue sea and sails.