Short vs Long unit label format

jacopo_chiappetti
Enthusiast
Enthusiast

Short vs Long unit label format

jacopo_chiappetti
Enthusiast
Enthusiast

I've an addin that inserts some values in istances converting from units stored in db in those of the project

e.g. millimeters in db -> meters in project

All values stored in db have an units field in "short" format: e.g. "mm" for millimeters, "m2" for cube meters 

Till 2022 version I used LabelUtils.GetLabelFor(Autodesk.Revit.DB.FormatOptions.GetValidUnitSymbols(parameter .DisplayUnitType).Where(Function(ByVal u As UnitSymbolType) u <> UnitSymbolType.UST_NONE).First()) to retrieve the label for every parameter in "short" format as in db

In 2022 version, if I use the new LabelUtils.GetLabelForUnit(parameter.GetUnitTypeId), what I've is the "long" format: e.g. "metri quadri" for m2, "millimeters" for mm

is there a way to retrieve the "short" format again?

 

0 Likes
Reply
Accepted solutions (2)
1,839 Views
17 Replies
Replies (17)

jeremy_tammik
Autodesk
Autodesk

Before we discuss your question, one short quick clarification: no length data at all is stored in the db in millimetres. All lengths are always stored in the hard-coded internal database length unit, which happens to be imperial feet. You may be referring to your own external db when you say 'db'. In any case, all lengths in the Revit database end up in feet.

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes

jacopo_chiappetti
Enthusiast
Enthusiast

Hi Tammik,

that is clear to me, all the Revit conversion system is clear to me

sorry for misunderstanding, when I wrote "db" I intended my db (sqlserver) with some data and values: all records have a field with units expressed in short format e.g. "mm" but now the only units label I could retrieve from Revit give me a "long" value e.g. "millimeters" so I couldn't match them and verify if I've to convert my db values or not.

I found a workaround compiling an XML, with 2019 and 2021 API, cycling on all units and writing labels in their different format: 

 

 

 

<UM>
<CODICE>2</CODICE>
<DESCRIZIONE>DUT_MILLIMETERS</DESCRIZIONE>
<LABEL>mm</LABEL>
<LABEL_2021>mm</LABEL_2021>
<LABEL_2022>Millimetri</LABEL_2022>
</UM>

<UM>
<CODICE>66</CODICE>
<DESCRIZIONE>DUT_CUBIC_METERS_PER_HOUR</DESCRIZIONE>
<LABEL>m³/h</LABEL>
<LABEL_2021>CMH</LABEL_2021>
<LABEL_2022>Metri cubi all'ora</LABEL_2022>
</UM>

 

 

LABEL is what I had in the past in Revit and what in my db

LABEL_2021-2022 is what I've in Revit 2021 and 2022

 

but I should prefer a way to retrieve directly the "old" (what in LABEL) short format if possible

thanks

0 Likes

jacopo_chiappetti
Enthusiast
Enthusiast

hi Jeremy, have you seen my answer and have any idea how to retrieve the "old" short format? thanks

0 Likes

RPTHOMAS108
Mentor
Mentor
Accepted solution

There is only the symbol of the unit and the name of the unit.

 

For each unit there can be multiple symbols such as ft, '. Although for metric units there is usually only one symbol for each e.g. mm.

 

You can get the possible unit symbols for each Spec via the Format options of that Spec, as in below code:

 

Private Function Obj_210716a(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData,
ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) As Result

        Dim UIDoc As UIDocument = commandData.Application.ActiveUIDocument
        If UIDoc Is Nothing Then Return Result.Cancelled Else
        Dim IntDoc As Document = UIDoc.Document

        Dim R As Reference = Nothing
        Try
            R = UIDoc.Selection.PickObject(Selection.ObjectType.Element, "Select element")
        Catch ex As Exception
            Return Result.Cancelled
        End Try

        Dim El As Element = IntDoc.GetElement(R)

        For Each P As Parameter In El.Parameters

            Dim Sb As New Text.StringBuilder()
            If P.StorageType <> StorageType.Double Then Continue For Else

            Dim Units As Units = IntDoc.GetUnits

            If UnitUtils.IsMeasurableSpec(P.Definition.GetDataType) = False Then Continue For Else
            Dim Fo As FormatOptions = Units.GetFormatOptions(P.Definition.GetDataType)

            Sb.Append(P.Definition.Name & ",")

            If Fo.CanHaveSymbol Then
                Dim SymbolsSet As List(Of ForgeTypeId) = Fo.GetValidSymbols()
                For i = 0 To SymbolsSet.Count - 1
                    Dim Fs As ForgeTypeId = SymbolsSet(i)
                    If Not String.IsNullOrEmpty(Fs.TypeId) Then ' No symbol option is empty ForgeTypeId.TypeId
                        If i = SymbolsSet.Count - 1 Then
                            Sb.Append(LabelUtils.GetLabelForSymbol(Fs))
                        Else
                            Sb.Append(LabelUtils.GetLabelForSymbol(Fs) & ",")
                        End If
                    End If
                Next
                If Not String.IsNullOrEmpty(Fo.GetSymbolTypeId().TypeId) Then
                    'When no unit symbol is set the ForgeTypeId.TypeId is empty string which will cause exception for below
                    Sb.Append(" (Current set = " & LabelUtils.GetLabelForSymbol(Fo.GetSymbolTypeId()) & ")")
                End If
            Else
                Sb.Append("No symbol")
            End If

            Debug.WriteLine(Sb.ToString)
        Next
        Return Result.Succeeded
    End Function

 

Example output:

Height 1,mm
Capital Top Offset,mm
Volume,m³ (Current set = m³)
Base Offset,mm
Top Offset,mm
Base Offset,mm
Length,mm
Top Offset,mm

 

In the example above I get Units object from the document. You can also create a Units instance by either specifying metric or imperial to the constructor of Units. However this doesn't keep sync with how the units are changed in the project (it just provides a default set).

 

In theory rather than looking at specific parameters as above you could iterate the Specs in the SpectTypeId class (via reflection), create FormatOptions during each iteration and then get the symbols for each via FormatOptions.GetValidSymbols.

 

For metric system as previously mentioned there is usually only one symbol for each (what you note as short form).

jacopo_chiappetti
Enthusiast
Enthusiast

really thanks

they work both: for a single par and also reading SpecTypeId props from reflection

there are old short formats not equals but ok

0 Likes

jeremy_tammik
Autodesk
Autodesk

Many thanks to Richard for the solution and to Jacobo for testing and verifying. Would you like to share the code you used and the complete output, in case anyone else has use for it as well? Thank you!

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes

jacopo_chiappetti
Enthusiast
Enthusiast

Hi Jeremy,

 

for the first step, read the unit label for parameters, I used the code provided by Richard

Dim ump As String = ""
Dim u As New Units(UnitSystem.Metric)
Dim fo As FormatOptions = u.GetFormatOptions(par.Definition.GetDataType)
If fo.CanHaveSymbol Then
    Dim SymbolsSet As List(Of ForgeTypeId) = fo.GetValidSymbols()
    For i = 0 To SymbolsSet.Count - 1
        Dim ss As ForgeTypeId = SymbolsSet(i)
        If Not String.IsNullOrEmpty(ss.TypeId) Then
            If i = SymbolsSet.Count - 1 Then
                ump = LabelUtils.GetLabelForSymbol(ss)
            Else
                If ump <> "" Then ump &= ","
                ump &= LabelUtils.GetLabelForSymbol(ss)
            End If
        End If
    Next
    If Not String.IsNullOrEmpty(fo.GetSymbolTypeId().TypeId) Then
        ump = LabelUtils.GetLabelForSymbol(fo.GetSymbolTypeId())
    End If
End If

 

after that I tried to use reflection to read SpecTypeId class, to find the matching labels comes from parameters, but for some reason I cannot retrieve all the unit labels

Dim ST As Type = GetType(SpecTypeId)
For Each PI In ST.GetProperties(Reflection.BindingFlags.Public + Reflection.BindingFlags.Static)
    Try
        Dim utId As ForgeTypeId = PI.GetValue(ST)
        If utId <> SpecTypeId.Custom Then

            Dim u As New Units(UnitSystem.Metric)
            Dim fo As FormatOptions = u.GetFormatOptions(utId)

            If fo.CanHaveSymbol Then
                Dim SymbolsSet As List(Of ForgeTypeId) = fo.GetValidSymbols()
                For i = 0 To SymbolsSet.Count - 1
                    Dim ss As ForgeTypeId = SymbolsSet(i)
                    If Not String.IsNullOrEmpty(ss.TypeId) Then
                        If lblUM = LabelUtils.GetLabelForSymbol(ss) Then
                            codUM = utId
                            Exit For
                        End If
                    End If
                Next
                If Not String.IsNullOrEmpty(fo.GetSymbolTypeId().TypeId) Then
                    If lblUM = LabelUtils.GetLabelForSymbol(fo.GetSymbolTypeId()) Then
                        codUM = utId
                        Exit For
                    End If
                End If
            End If
        End If
    Catch ex As Exception

    End Try
Next

 

so, for this final part, I used the XML file to find the 2022 "long" label and, then, retrieve the ForgeTypeId

For Each utId As ForgeTypeId In UnitUtils.GetAllUnits
    Try
        Dim lut As String = LabelUtils.GetLabelForUnit(utId)
        If lut = lblUM Then
            codUM = utId
            Exit For
        End If
    Catch ex As Exception

    End Try
Next

 

I know it's not he best way but it works

again I can't understand why GetLabelForUnit gives a totally useless long label instead of a short world wide used label

0 Likes

jacopo_chiappetti
Enthusiast
Enthusiast

for example I cannot find "m³/h" 

 

0 Likes

jeremy_tammik
Autodesk
Autodesk

The Revit SDK Units sample does some magic to display the short units:

 

 

Screenshot 2021-07-19 at 15.15.51.png

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes

jacopo_chiappetti
Enthusiast
Enthusiast

Hi Jeremy,

as far as I understand "units" is for old Revit versions and doesn't work with 2022 because of deprecated classes

but at the end it did the same I did in old versions of my addin (and it worked since 2022): UnitUtils.GetAllSpecs() and then LabelUtils.GetLabelForSpec(specTypeId)

again, my code now works fine for a lot of units (mm, m, L/s) but it doesn't find few of them like m³/h

 

Dim ST As Type = GetType(SpecTypeId)
For Each PI In ST.GetProperties(Reflection.BindingFlags.Public + Reflection.BindingFlags.Static)
    Try
        Dim utId As ForgeTypeId = PI.GetValue(ST)
        If utId <> SpecTypeId.Custom Then
            Dim u As New Units(UnitSystem.Metric)
            Dim fo As FormatOptions = u.GetFormatOptions(utId)
            If fo.CanHaveSymbol Then
                Dim SymbolsSet As List(Of ForgeTypeId) = fo.GetValidSymbols()
                For i = 0 To SymbolsSet.Count - 1
                    Dim ss As ForgeTypeId = SymbolsSet(i)
                    If Not String.IsNullOrEmpty(ss.TypeId) Then
                        If lblUM = LabelUtils.GetLabelForSymbol(ss) Then
                            codUM = fo.GetUnitTypeId() 'FIND IT!!!
                            Exit For
                        End If
                    End If
                Next
            End If
        End If
    Catch ex As Exception

    End Try
Next

 

0 Likes

jeremy_tammik
Autodesk
Autodesk

All the SDK samples are updated for the new version, every release, so Units has been updated for Revit 2022.

 

Yes, it is pretty frustrating and the documentation is not very helpful.

 

I found: 

 

  • Autodesk.Revit.DB.ForgeTypeId: autodesk.unit.unit:cubicMetersPerHour-1.0.1
  • CUBIC_METERS_PER_HOUR
  • Cubic meters per hour

 

I thought I could retrieve the 'm^3/h' string by using 

 

 

  Units units_metric = new Units( UnitSystem.Metric );

  IList<ForgeTypeId> units = UnitUtils.GetAllUnits();

  foreach( ForgeTypeId fti in units )
  {
    UnitFormatUtils.Format( units_metric, fti, 1.0, false );
  }

 

 

However, it throws an exception saying that the unit:cubicMetersPerHour unit is not measurable.

 

I am sure there is a simple solution, but no easy to find...

 

Some test code of mine is in The Building Coder samples Util.ListForgeTypeIds method.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes

jeremy_tammik
Autodesk
Autodesk

I also asked the devteam for you.

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open

david_becroft
Autodesk
Autodesk
Accepted solution

Good morning,

 

The code posted above misses "m³/h" because it examines only the default unit for each spec (i.e., the result of Units.GetFormatOptions(spec).GetUnitTypeId().

 

Mr. Tammik, you get an exception from UnitFormatUtils.Format(...) because you pass it a unit identifier where it expects a spec identifier. Try iterating over UnitUtils.GetAllMeasurableSpecs() instead of UnitUtils.GetAllUnits().

 

I see you're working in VB so please forgive me for posting my reply in C#! Here's a macro to see the text of the first symbol for each unit that has one:

 

foreach (ForgeTypeId unit in UnitUtils.GetAllUnits())
{
   foreach (ForgeTypeId symbol in FormatOptions.GetValidSymbols(unit))
   {
      if (!symbol.Empty())
      {
         TaskDialog.Show(unit.TypeId, LabelUtils.GetLabelForSymbol(symbol));
         break;
      }
   }
}

david_becroft
Autodesk
Autodesk

I don't think that macro is exactly what you needed but I hope it helps illustrate the API. I wanted to point out that instead of using reflection to iterate the properties of class SpecTypeId you can more simply call UnitUtils.GetAllMeasurableSpecs() to iterate all parameter data types quantified by units of measurement. Incidentally, SpecUtils.GetAllSpecs() lets you iterate all parameter data types, including the ones that aren't quantified by units of measurement, such as Text or Yes/No. If you just want to know about units and their symbols, you can iterate over UnitUtils.GetAllUnits() and then use FormatOptions.GetValidSymbols(unit). The LabelUtils class methods let you exchange the ForgeTypeId identifier of a unit, symbol, or spec for the friendly name of that item as displayed in the user interface, e.g. LabelUtils.GetLabelForUnit(UnitTypeId.Millimeters) -> "Millimeters" or LabelUtils.GetLabelForSymbol(SymbolTypeId.Mm) -> "mm".

0 Likes

jacopo_chiappetti
Enthusiast
Enthusiast

Hi David,

I used UnitUtils.GetAllMeasurableSpecs instead of use reflection and it works but gives me the same number of units labels (obviously).

For Each utId As ForgeTypeId In UnitUtils.GetAllMeasurableSpecs()
    Dim u As New Units(UnitSystem.Metric)
    Dim fo As FormatOptions = u.GetFormatOptions(utId)
    If fo.CanHaveSymbol Then
        Dim SymbolsSet As List(Of ForgeTypeId) = fo.GetValidSymbols()
        For i = 0 To SymbolsSet.Count - 1
            Dim ss As ForgeTypeId = SymbolsSet(i)
            If Not String.IsNullOrEmpty(ss.TypeId) Then
                TaskDialog.Show(utId.TypeId, LabelUtils.GetLabelForSymbol(ss))
            End If
        Next
    End If
Next


I already used UnitUtils.GetAllUnits (no problems with C) but as I said before it works but doesn't retrieve some units label (again m³/h)

at the end there are many methods to retrieve units labels but seems that all of them miss to retrive some labels

thanks

0 Likes

jacopo_chiappetti
Enthusiast
Enthusiast

I was wrong, just put the same "break;" of your code ... obviously it retrieves, as you said, the first label

but without it now I've all the units labels, also m³/h

thanks

For Each unit As ForgeTypeId In UnitUtils.GetAllUnits()
    For Each symbol As ForgeTypeId In FormatOptions.GetValidSymbols(unit)
        If Not symbol.Empty() Then
            If lblUM = LabelUtils.GetLabelForSymbol(symbol) Then
                codUM = unit 'FOUND!!!
                Exit For
            End If
        End If
    Next
    If Not codUM Is Nothing Then Exit For
Next

jacopo_chiappetti
Enthusiast
Enthusiast

only as a reference in case it may serve others

the above code, that read units labels from parameters , reading the definition doesn't reflect units setup in the project

to read family parameters units labels as set in the project this the code

For Each symbol As ForgeTypeId In FormatOptions.GetValidSymbols(par.GetUnitTypeId)
    If Not symbol.Empty() Then
        ump = LabelUtils.GetLabelForSymbol(symbol) 'FOUND
        Exit For
    End If
Next

  

0 Likes