Unit Conversion and Display String Formatting

AU went very well for me, and I think this was the one I liked most of all so far, to my own surprise.
Now I am already at the next conference in Moscow, from where I continue to Tel Aviv tomorrow.
December is always my monster travelling month, and I never get to prepare for Christmas or enjoy the dark and cosy celebration of

Advent
.
But I really did have fun and enjoy AU in Las Vegas.

Sunday morning my colleague Marat Mirgaleev invited me to join him in his weekly volleyball game, which was a wonderful break from the conference presentation preparation.
Marat is also a member of the ADN DevTech team and spelled Марат Миргалеев in Cyrillic.
Another Autodesk colleague who also joined was Rustam Ibragimov, Рустам Ибрагимов.
Here are Rustam, Marat, I and our all-time star, the volleyball herself:

Rustam, Marat, Jeremy and the volleyball

Here we are now in the Autodesk Moscow office:

DevDay conference in Moscow

Fittingly enough, here is a

question
from
Russia, by Victor Chekalin, or Виктор Чекалин in Cyrillic, on formatting a floating point number as a display string using the current project units.
This issue has cropped up several times recently, and various solutions based on the same principle have been suggested, among others by Joe Offord of

Enclos
,
who already shared insights on accessing

curtain wall geometry
,

speeding up the interactive selection process
,

mirroring in a new family and changing the active view
, and

constructing a planar face transform
.

All of the solutions to this problem I have seen revolve around stuffing in the value to format into an unused parameter picked up from some arbitrary database element and then calling the AsValueString method on it.
The Parameter class provides this functionality, but unfortunately the API does not include any stand-alone access to it.
Here is Victor’s initial query:

Question: I need to convert a numeric value to a corresponding display string honouring the current Revit ProjectUnit setting. I cannot find how to do it in the Revit help and began search the answer in your amazing site. I found the

Unit Conversion
tool
and thought it would fulfil my need, but I was wrong :(

Looking at the Unit Converter code, I discovered that retrieving scale factor to internal units is not easy and you used some trick to get it: you change ProjectUnit, write “1” to family parameter, read value from this parameter, change ProjectUnit back. It works but is really hard and is not an official way.

Answer: Try something like this on some otherwise unused length parameter ‘p’:


  Dim value As String = "=2' + 4'/3"
  Dim t As New Transaction(doc, "Format Length")
  t.Start()
  p.SetValueString(value)
  value = p.AsValueString
  t.RollBack()
  Return value

In fact, Joe provided the following helper methods based on this idea implemented in VB:

  • StringValueString – Converts a string to AsValueString equivalent.
  • DblValueString – Converts a double to AsValueString equivalent.
  • ValueStringDbl – Converts a ValueString to a double.

All three of these methods create and then roll back a temporary transaction to perform their task.

Here is the full implementation of the first of these, StringValueString:


  Public Shared Function StringValueString( _
    ByVal doc As Document, _
    ByVal value As String) As String
 
    ' Locate the arbitrary Length parameter
 
    Dim p As Parameter _
      = doc.ProjectInformation.Parameter( _
      "Parameter Name")
 
    If p Is Nothing Then
      TaskDialog.Show( _
        "Revit", _
        "Missing Project Parameter: Parameter Name")
      Return value
    End If
 
    Dim tr As New Transaction(doc)
    tr.Start("Format Length")
 
    Try
      p.SetValueString(value)
      value = p.AsValueString
    Catch ex As Exception
 
    End Try
 
    tr.RollBack()
 
    Return value
 
  End Function

For the second, DblValueString, simply replace the input argument by a floating-point value ‘ByVal value As Double’ and the two lines to perform the actual conversion by


p.Set(value)
sValueString = p.AsValueString

Finally, for the third, ValueStringDbl, the input argument ‘value’ is again a string and the conversion to the floating-point return value is performed by


p.SetValueString(value)
dValue = p.AsDouble

Response: Thanks for the answer.

I wrote some simple extension methods for the Revit API Parameter class to get value in project units and in meters value.
Now it works with Length, Volume and Area (now I don’t need any more).
It would take much time to add support for all unit conversions.

Here is the entire implementation of my

ParameterUnitConverter class
.
It defines the following methods and data:

  • AsProjectUnitTypeDouble – Parameter extension method to retrieve double value parameter in ProjectUnits.
  • AsMetersValue – Parameter extension method to retrieve double value of parameter in meters unit, i.e. length in meters, area in square meters and volume in cubic meters.
  • ConvertParameterTypeToUnitType – static method to return the corresponding UnitType for a given ParameterType.
  • _map_parameter_type_to_unit_type – a dictionary mapping ParameterType enumeration values to the corresponding UnitType ones.

Here is the complete implementation of this:


public static class ParameterUnitConverter
{
  private const double METERS_IN_FEET = 0.3048;
 
  public static double AsProjectUnitTypeDouble(
    this Parameter param )
  {
    if( param.StorageType != StorageType.Double )
      throw new NotSupportedException(
        "Parameter does not have double value" );
 
    double imperialValue = param.AsDouble();
 
    Document document = param.Element.Document;
 
    UnitType ut = ConvertParameterTypeToUnitType(
      param.Definition.ParameterType );
 
    FormatOptions fo = document.ProjectUnit
      .get_FormatOptions( ut );
 
    DisplayUnitType dut = fo.Units;
 
    // Unit Converter
    // http://www.asknumbers.com
 
    switch( dut )
    {
      #region Length
 
      case DisplayUnitType.DUT_METERS:
        return imperialValue * METERS_IN_FEET; //feet
      case DisplayUnitType.DUT_CENTIMETERS:
        return imperialValue * METERS_IN_FEET * 100;
      case DisplayUnitType.DUT_DECIMAL_FEET:
        return imperialValue;
      case DisplayUnitType.DUT_DECIMAL_INCHES:
        return imperialValue * 12;
      case DisplayUnitType.DUT_FEET_FRACTIONAL_INCHES:
        NotSupported( dut );
        break;
      case DisplayUnitType.DUT_FRACTIONAL_INCHES:
        NotSupported( dut );
        break;
      case DisplayUnitType.DUT_METERS_CENTIMETERS:
        return imperialValue * METERS_IN_FEET; //feet
      case DisplayUnitType.DUT_MILLIMETERS:
        return imperialValue * METERS_IN_FEET * 1000;
 
      #endregion // Length
 
      #region Area
 
      case DisplayUnitType.DUT_SQUARE_FEET:
        return imperialValue;
      case DisplayUnitType.DUT_ACRES:
        return imperialValue * 1 / 43560.039;
      case DisplayUnitType.DUT_HECTARES:
        return imperialValue * 1 / 107639.104;
      case DisplayUnitType.DUT_SQUARE_CENTIMETERS:
        return imperialValue * Math.Pow( METERS_IN_FEET * 100, 2 );
      case DisplayUnitType.DUT_SQUARE_INCHES:
        return imperialValue * Math.Pow( 12, 2 );
      case DisplayUnitType.DUT_SQUARE_METERS:
        return imperialValue * Math.Pow( METERS_IN_FEET, 2 );
      case DisplayUnitType.DUT_SQUARE_MILLIMETERS:
        return imperialValue * Math.Pow( METERS_IN_FEET * 1000, 2 );
 
      #endregion // Area
 
      #region Volume
      case DisplayUnitType.DUT_CUBIC_FEET:
        return imperialValue;
      case DisplayUnitType.DUT_CUBIC_CENTIMETERS:
        return imperialValue * Math.Pow( METERS_IN_FEET * 100, 3 );
      case DisplayUnitType.DUT_CUBIC_INCHES:
        return imperialValue * Math.Pow( 12, 3 );
      case DisplayUnitType.DUT_CUBIC_METERS:
        return imperialValue * Math.Pow( METERS_IN_FEET, 3 );
      case DisplayUnitType.DUT_CUBIC_MILLIMETERS:
        return imperialValue * Math.Pow( METERS_IN_FEET * 1000, 3 );
      case DisplayUnitType.DUT_CUBIC_YARDS:
        return imperialValue * 1 / Math.Pow( 3, 3 );
      case DisplayUnitType.DUT_GALLONS_US:
        return imperialValue * 7.5;
      case DisplayUnitType.DUT_LITERS:
        return imperialValue * 28.31684;
 
      #endregion // Volume
 
      default:
        NotSupported( dut );
        break;
    }
 
    throw new NotSupportedException();
  }
  public static double AsMetersValue(
    this Parameter param )
  {
    if( param.StorageType != StorageType.Double )
      throw new NotSupportedException(
        "Parameter does not have double value" );
 
    double imperialValue = param.AsDouble();
 
    UnitType ut = ConvertParameterTypeToUnitType(
      param.Definition.ParameterType );
 
    switch( ut )
    {
      case UnitType.UT_Length:
        return imperialValue * METERS_IN_FEET;
 
      case UnitType.UT_Area:
        return imperialValue * Math.Pow(
          METERS_IN_FEET, 2 );
 
      case UnitType.UT_Volume:
        return imperialValue * Math.Pow(
          METERS_IN_FEET, 3 );
    }
    throw new NotSupportedException();
  }
 
  public static UnitType
    ConvertParameterTypeToUnitType(
      ParameterType parameterType )
  {
    if( _map_parameter_type_to_unit_type.ContainsKey(
      parameterType ) )
    {
      return _map_parameter_type_to_unit_type[
        parameterType];
    }
    else
    {
      // If we made it this far, there's 
      // no entry in the dictionary.
 
      throw new ArgumentException(
        "Cannot convert ParameterType '"
          + parameterType.ToString()
          + "' to a UnitType." );
    }
  }
 
  static Dictionary<ParameterType, UnitType>
    _map_parameter_type_to_unit_type
      = new Dictionary<ParameterType, UnitType>()
  {
    // This data could come from a file, 
    // or (better yet) from Revit itself...
 
    {ParameterType.Angle, UnitType.UT_Angle},
    {ParameterType.Area, UnitType.UT_Area},
    {ParameterType.AreaForce, UnitType.UT_AreaForce},
    {ParameterType.AreaForcePerLength, UnitType.UT_AreaForcePerLength},
    //map.Add(UnitType.UT_AreaForceScale, ParameterType.???);
    {ParameterType.ColorTemperature, UnitType.UT_Color_Temperature},
    {ParameterType.Currency, UnitType.UT_Currency},
    //map.Add(UnitType.UT_DecSheetLength, ParameterType.???);
    {ParameterType.ElectricalApparentPower, UnitType.UT_Electrical_Apparent_Power},
    {ParameterType.ElectricalCurrent, UnitType.UT_Electrical_Current},
    {ParameterType.ElectricalEfficacy, UnitType.UT_Electrical_Efficacy},
    {ParameterType.ElectricalFrequency, UnitType.UT_Electrical_Frequency},
    {ParameterType.ElectricalIlluminance, UnitType.UT_Electrical_Illuminance},
    {ParameterType.ElectricalLuminance, UnitType.UT_Electrical_Luminance},
    {ParameterType.ElectricalLuminousFlux, UnitType.UT_Electrical_Luminous_Flux},
    {ParameterType.ElectricalLuminousIntensity, UnitType.UT_Electrical_Luminous_Intensity},
    {ParameterType.ElectricalPotential, UnitType.UT_Electrical_Potential},
    {ParameterType.ElectricalPower, UnitType.UT_Electrical_Power},
    {ParameterType.ElectricalPowerDensity, UnitType.UT_Electrical_Power_Density},
    {ParameterType.ElectricalWattage, UnitType.UT_Electrical_Wattage},
    {ParameterType.Force, UnitType.UT_Force},
    {ParameterType.ForceLengthPerAngle, UnitType.UT_ForceLengthPerAngle},
    {ParameterType.ForcePerLength, UnitType.UT_ForcePerLength},
    //map.Add(UnitType.UT_ForceScale, ParameterType.???);
    {ParameterType.HVACAirflow, UnitType.UT_HVAC_Airflow},
    {ParameterType.HVACAirflowDensity, UnitType.UT_HVAC_Airflow_Density},
    {ParameterType.HVACAirflowDividedByCoolingLoad, UnitType.UT_HVAC_Airflow_Divided_By_Cooling_Load},
    {ParameterType.HVACAirflowDividedByVolume, UnitType.UT_HVAC_Airflow_Divided_By_Volume},
    {ParameterType.HVACAreaDividedByCoolingLoad, UnitType.UT_HVAC_Area_Divided_By_Cooling_Load},
    {ParameterType.HVACAreaDividedByHeatingLoad, UnitType.UT_HVAC_Area_Divided_By_Heating_Load},
    {ParameterType.HVACCoefficientOfHeatTransfer, UnitType.UT_HVAC_CoefficientOfHeatTransfer},
    {ParameterType.HVACCoolingLoad, UnitType.UT_HVAC_Cooling_Load},
    {ParameterType.HVACCoolingLoadDividedByArea, UnitType.UT_HVAC_Cooling_Load_Divided_By_Area},
    {ParameterType.HVACCoolingLoadDividedByVolume, UnitType.UT_HVAC_Cooling_Load_Divided_By_Volume},
    {ParameterType.HVACCrossSection, UnitType.UT_HVAC_CrossSection},
    {ParameterType.HVACDensity, UnitType.UT_HVAC_Density},
    {ParameterType.HVACDuctSize, UnitType.UT_HVAC_DuctSize},
    {ParameterType.HVACEnergy, UnitType.UT_HVAC_Energy},
    {ParameterType.HVACFactor, UnitType.UT_HVAC_Factor},
    {ParameterType.HVACFriction, UnitType.UT_HVAC_Friction},
    {ParameterType.HVACHeatGain, UnitType.UT_HVAC_HeatGain},
    {ParameterType.HVACHeatingLoad, UnitType.UT_HVAC_Heating_Load},
    {ParameterType.HVACHeatingLoadDividedByArea, UnitType.UT_HVAC_Heating_Load_Divided_By_Area},
    {ParameterType.HVACHeatingLoadDividedByVolume, UnitType.UT_HVAC_Heating_Load_Divided_By_Volume},
    {ParameterType.HVACPower, UnitType.UT_HVAC_Power},
    {ParameterType.HVACPowerDensity, UnitType.UT_HVAC_Power_Density},
    {ParameterType.HVACPressure, UnitType.UT_HVAC_Pressure},
    {ParameterType.HVACRoughness, UnitType.UT_HVAC_Roughness},
    {ParameterType.HVACSlope, UnitType.UT_HVAC_Slope},
    {ParameterType.HVACTemperature, UnitType.UT_HVAC_Temperature},
    {ParameterType.HVACVelocity, UnitType.UT_HVAC_Velocity},
    {ParameterType.HVACViscosity, UnitType.UT_HVAC_Viscosity},
    {ParameterType.Length, UnitType.UT_Length},
    {ParameterType.LinearForce, UnitType.UT_LinearForce},
    {ParameterType.LinearForceLengthPerAngle, UnitType.UT_LinearForceLengthPerAngle},
    {ParameterType.LinearForcePerLength, UnitType.UT_LinearForcePerLength},
    // map.Add(UnitType.UT_LinearForceScale, ParameterType.???);
    {ParameterType.LinearMoment, UnitType.UT_LinearMoment},
    // map.Add(UnitType.UT_LinearMomentScale, ParameterType.???);
    {ParameterType.Moment, UnitType.UT_Moment},
    ///map.Add(UnitType.UT_MomentScale, ParameterType.???);
    {ParameterType.Number, UnitType.UT_Number},
    {ParameterType.PipeSize, UnitType.UT_PipeSize},
    {ParameterType.PipingDensity, UnitType.UT_Piping_Density},
    {ParameterType.PipingFlow, UnitType.UT_Piping_Flow},
    {ParameterType.PipingFriction, UnitType.UT_Piping_Friction},
    {ParameterType.PipingPressure, UnitType.UT_Piping_Pressure},
    {ParameterType.PipingRoughness, UnitType.UT_Piping_Roughness},
    {ParameterType.PipingSlope, UnitType.UT_Piping_Slope},
    {ParameterType.PipingTemperature, UnitType.UT_Piping_Temperature},
    {ParameterType.PipingVelocity, UnitType.UT_Piping_Velocity},
    {ParameterType.PipingViscosity, UnitType.UT_Piping_Viscosity},
    {ParameterType.PipingVolume, UnitType.UT_Piping_Volume},
    //map.Add(UnitType.UT_SheetLength, ParameterType.???);
    //map.Add(UnitType.UT_SiteAngle, ParameterType.???);
    {ParameterType.Slope, UnitType.UT_Slope},
    {ParameterType.Stress, UnitType.UT_Stress},
    {ParameterType.TemperalExp, UnitType.UT_TemperalExp},
    {ParameterType.UnitWeight, UnitType.UT_UnitWeight},
    {ParameterType.Volume, UnitType.UT_Volume},
    {ParameterType.WireSize, UnitType.UT_WireSize},
  };
}

I used some functions from the Unit converter.
Here is an external command

sample to test it
by
iterating over and applying it to all floating point valued parameters on a selected element.

Hope you’ll find this useful.

Many thanks to Joe and Victor for putting together and sharing these nice solutions!

I added the ParameterUnitConverter class to The Building Coder samples and defined a new external command CmdParameterUnitConverter based on Victor’s code to test it.
Here is
version 2012.0.96.0 of
The Building Coder samples including the new utility class and command.

This is the output generated by the command in the Visual Studio debug output window on selecting a wall element in the rac_basic_sample_project.rvt sample model:


Parameter name: Top Extension Distance
Parameter value (imperial): 0
Parameter unit value: 0
Parameter AsValueString: 0.0
Parameter name: Length
Parameter value (imperial): 45.5
Parameter unit value: 13868.4
Parameter AsValueString: 13868.4
Parameter name: Base Extension Distance
Parameter value (imperial): 0
Parameter unit value: 0
Parameter AsValueString: 0.0
Parameter name: Top Offset
Parameter value (imperial): 0
Parameter unit value: 0
Parameter AsValueString: 0.0
Parameter name: Volume
Parameter value (imperial): 455.9586023831
Parameter unit value: 12.911309795985
Parameter AsValueString: 12.911 m³
Parameter name: Unconnected Height
Parameter value (imperial): 18.0446194225722
Parameter unit value: 5500
Parameter AsValueString: 5500.0
Parameter name: Base Offset
Parameter value (imperial): 0
Parameter unit value: 0
Parameter AsValueString: 0.0
Parameter name: Area
Parameter value (imperial): 694.880910031848
Parameter unit value: 64.5565489799251
Parameter AsValueString: 64.557 m²

Here, the parameter value labelled ‘imperial’ is the

internal Revit database unit
, e.g.

feet for length
.
Please note that not all internal database units are imperial.
In fact, only length is measured in feet, and thus also area and volume.
Other internal units are
SI-based.


Comments

7 responses to “Unit Conversion and Display String Formatting”

  1. Thanks for publish my question and solution. And of course for your help.

  2. Caroline Avatar
    Caroline

    Thanks for the ParameterUnitConverter code. I have used it successfully in my applications and now I’m upgrading to 2014 and I’m getting two Errors at the following lines:
    FormatOptions fo = document.ProjectUnit.get_FormatOptions(ut);
    DisplayUnitType dut = fo.Units;
    I’m trying to understand how the new units classes should be used instead but I’m having some trouble with getting this to work. Can you please help me?

  3. Dear Caroline,
    Yes, indeed, the unit handling has been completely overhauled in the Revit 2014 API.
    Have you looked at the What’s New section in the Revit API help file? I normally discusses such changes.
    Have you searched for occurrences of these methods in the Revit 2013 and 2014 SDK samples, and compared the differences there? That would provide an exact mapping from old to new.
    Those are the two places I would start looking.
    Unfortunately, the current pre-release does not include the most up-to-date SDK, but it will be posted any day now on the ADN web site.
    Cheers, Jeremy.

  4. Caroline Avatar
    Caroline

    Thanks Jeremy I’ll follow up those leads.
    Caroline

  5. Caroline Avatar
    Caroline

    Hi Jeremy,
    I checked out your leads and found that the method GetDisplayUnitType does the trick. So this little correction gets it rolling again
    //FormatOptions fo = document.ProjectUnit.get_FormatOptions(ut);
    Units units = document.GetUnits();
    DisplayUnitType dut = units.GetDisplayUnitType();
    I suspect that ParameterUnitConverter could be simplified or even replaced with some of the new methods but it will have to be another day for me to check this out. Thanks again.
    Caroline

  6. Dear Caroline,
    Thank you for your appreciation. I am very glad to hear it worked!
    Cheers, Jeremy.

  7. Anatoly Vasilenko Avatar
    Anatoly Vasilenko

    Dear Jeremy,
    Can you advice, please, how to put Lighting Load Density and Power Load Density by API
    Thanks

Leave a Reply to CarolineCancel reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading