ADN Open CIS
Сообщество программистов Autodesk в СНГ

31/08/2013

Parameter.DisplayUnitType

В одном из своих проектов мне нужно было получить все параметры практически всех элементов в проекте, а также все свойства каждого параметра. Проект может содержать несколько десятков тысяч элементов, а каждый элемент может содержать несколько десятков параметров. 

Очевидно, что такой перебор занимает довольно продолжительное время.

Проблема, с которой я столкнулся, было определение свойства DisplayUnitType (Единица измерения) для параметра. Дело в том, что не все параметры имеют единицы измерения. В случае попытки получить свойство DisplayUnitType у параметра, для которого единица измерения не предусмотрена, то Revit выбрасывает исключение Autodesk.Revit.Exceptions.InvalidOperationException. Заранее определить, предусмотрена для параметра единица измерения или нет – нельзя.

Таким образом, приходится перехватывать исключение для каждого параметра с помощью блока try…catch

Код - C#: [Выделить]
  1.             foreach (var parameter in element.Parameters)
  2.             {
  3.                 try
  4.                 {
  5.                     var dut = parameter.DisplayUnitType;
  6.                 }
  7.                 catch (Autodesk.Revit.Exceptions.InvalidOperationException)
  8.                 {
  9.                                        
  10.                 }
  11.             }

Но, перехват исключения - это довольно дорогостоящая операция и занимает относительно продолжительное время. При десятках тысяч итераций, время, затрачиваемое на перехват исключения, может оказаться довольно ощутимым.

Я решил исправить это недоразумение, и выяснить, можно ли определить заранее, предусмотрена для параметра единица измерения или нет.

С помощью бесплатного декомпилятора dotPeek я открыл сборку RevitAPI.dll. Первым делом я решил посмотреть на свойство DisplayUnitType класса Parameter.  

Код - C#: [Выделить]
  1.  
  2. public DisplayUnitType DisplayUnitType
  3. {
  4.   get
  5.   {
  6.     Definition definition = this.Definition;
  7.     ParamTypeSpec paramTypeSpec1;
  8.     if ((ParamTypeEnum) *(int*)
  9.       definition.getParamTypeSpec(&paramTypeSpec1)
  10.       != (ParamTypeEnum) 15)
  11.     {
  12.       throw new Autodesk.Revit.Exceptions
  13.         .InvalidOperationException(new FunctionId(
  14.           "n:\\build\\2013_ship_x64_inst_20120221_2030\\source\\api\\revitapi\\objects\\parameters\\APIParameter.cpp",
  15.           581"Autodesk::Revit::DB::Parameter::DisplayUnitType::get"),
  16.           string.Empty);
  17.     }
  18.     else
  19.     {
  20.       ParamTypeSpec paramTypeSpec2;
  21.       return (DisplayUnitType)
  22.         \u003CModule\u003E.FormatOptions\u002EgetDisplayUnits(
  23.           \u003CModule\u003E.AUnits\u002EgetFormatOptions(
  24.             \u003CModule\u003E.ADocument\u002EgetAUnits(
  25.               (ADocument*) *(long*) this.m_pCDA),
  26.                 (UnitType.Enum) *(int*) ((IntPtr)
  27.                 definition.getParamTypeSpec(&paramTypeSpec2) + 8L)));
  28.     }
  29.   }
  30. }

 

Наиболее интересным здесь является строка if ((ParamTypeEnum) *(int*)   definition.getParamTypeSpec(&paramTypeSpec1)   != (ParamTypeEnum) 15). Здесь мы видим, что параметр имеет единицу измерения, если Definition.getParamTypeSpec(&paramTypeSpec1)   = 15.

Первое что пришло в голову – нужно с помощью рефлексии вызвать метод definition.getParamTypeSpec. Но, этот параметр является внутренним, помечен как unsafe, и к тому же в качестве параметра принимает указатель. К сожалению, моих знаний оказалось недостаточно, для того чтобы работать с такими параметрами с помощью рефлексии и я решил пойти другим путем.

Следующим шагом я решил посмотреть свойство ParameterType класса Definition.

Код - C#: [Выделить]
  1.  
  2. public override ParameterType ParameterType
  3. {
  4.   get
  5.   {
  6.     REVIT_MAINTAIN_STATE revitMaintainState;
  7.     \u003CModule\u003E.REVIT_MAINTAIN_STATE\u002E\u007Bctor\u007D(
  8.       &revitMaintainState);
  9.  
  10.     ParameterType parameterType;
  11.     try
  12.     {
  13.       ParamDef* paramDefPtr = this.m_pParamDef;
  14.       ParamTypeSpec paramTypeSpec;
  15.       \u003CModule\u003E.paramDefToParamType(
  16.         &paramTypeSpec, paramDefPtr);
  17.       switch (^(int&) @paramTypeSpec)
  18.       {
  19.       case 1:
  20.         parameterType = ParameterType.Text;
  21.         break;
  22.       case 6:
  23.         parameterType = ParameterType.URL;
  24.         break;
  25.       case 9:
  26.         parameterType = ParameterType.YesNo;
  27.         break;
  28.       case 11:
  29.         parameterType = ParameterType.Integer;
  30.         break;
  31.       case 13:
  32.         parameterType = ParameterType.Material;
  33.         break;
  34.       case 14:
  35.         parameterType = ParameterType.FamilyType;
  36.         break;
  37.       case 15:
  38.         switch (^(int&) ((IntPtr) &paramTypeSpec + 8))
  39.         {
  40.         case 0:
  41.           parameterType = ParameterType.Length;
  42.           break;
  43.         case 1:
  44.           parameterType = ParameterType.Area;
  45.           break;
  46.         case 2:
  47.           parameterType = ParameterType.Volume;
  48.           break;
  49.         case 3:
  50.           parameterType = ParameterType.Angle;
  51.           break;
  52.         case 4:
  53.           parameterType = ParameterType.Number;
  54.           break;
  55.         case 26:
  56.           parameterType = ParameterType.Force;
  57.           break;
  58.         case 27:
  59.           parameterType = ParameterType.LinearForce;
  60.           break;
  61.         case 28:
  62.           parameterType = ParameterType.AreaForce;
  63.           break;
  64.         case 29:
  65.           parameterType = ParameterType.Moment;
  66.           break;
  67.         default:
  68.           parameterType = (ParameterType) (
  69.             ^(int&) ((IntPtr) &paramTypeSpec + 8) + 100);
  70.           break;
  71.         }
  72.       case 16:
  73.         parameterType = ParameterType.NumberOfPoles;
  74.         break;
  75.       case 24:
  76.         parameterType = ParameterType.FixtureUnit;
  77.         break;
  78.       case 30:
  79.         parameterType = ParameterType.LoadClassification;
  80.         break;
  81.       default:
  82.         parameterType = ParameterType.Invalid;
  83.         break;
  84.       }
  85.     }
  86.     __fault
  87.     {
  88.       \u003CModule\u003E.___CxxCallUnwindDtor(
  89.         (__FnPtr<void (void*)>) __methodptr(REVIT_MAINTAIN_STATE\u002E\u007Bdtor\u007D),
  90.           (void*) &revitMaintainState);
  91.     }
  92.     \u003CModule\u003E.REVIT_MAINTAIN_STATE\u002E\u007Bdtor\u007D(
  93.       &revitMaintainState);
  94.     return parameterType;
  95.     }
  96.   }
  97. }

Здесь мы видим, что если paramTypeSpec = 15, то ParameterType является Длиной, Площадью и т.д. Таким образом, для того, чтобы определить, если ли у параметра единица измерения, достаточно проверить свойство ParameterType. Если это свойство равно Length, Area и т.д., то параметр имеет единицу измерения. В противном случае – нет.

Важно также обратить внимание на строку

Код - C#: [Выделить]
  1.  
  2.         default:
  3.           parameterType = (ParameterType) (
  4.             ^(int&) ((IntPtr) &paramTypeSpec + 8) + 100);
  5.           break;

Если посмотреть на определение перечисления ParameterType, то можно сказать, если числовое значение перечисления ParameterType > 100, то параметр также имеет единицу измерения.

В результате, я создал вспомогательный метод-расширение:

Код - C#: [Выделить]
  1.         public static class ParameterExtensions
  2.         {
  3.             public static bool HasDisplayUnitType(
  4.               this Parameter parameter)
  5.             {
  6.                 var parameterType =
  7.                     parameter.Definition.ParameterType;
  8.  
  9.                 switch (parameterType)
  10.                 {
  11.                     case ParameterType.Length:
  12.                     case ParameterType.Area:
  13.                     case ParameterType.Volume:
  14.                     case ParameterType.Angle:
  15.                     case ParameterType.Number:
  16.                     case ParameterType.Force:
  17.                     case ParameterType.LinearForce:
  18.                     case ParameterType.AreaForce:
  19.                     case ParameterType.Moment:
  20.                         return true;
  21.                 }
  22.  
  23.                 /* At the reflector I can see the following code
  24.                       default:
  25.                         parameterType = (ParameterType) 
  26.                           (^(int&) ((IntPtr) &paramTypeSpec 
  27.                           + 8) + 100);
  28.                         break;
  29.                     looking at the ParameterType enumeration 
  30.                     suggests that every parameter type whose 
  31.                     integer value is greater than 100 belongs 
  32.                     to paramTypeSpec = 15
  33.                 */
  34.                 return 100 < (int)parameterType;
  35.             }
  36.         }

Тестирование производительности

Слова об увеличении производительности были бы пустым звуком, без тестирования.

В первую очередь протестируем перехват исключения

Код - C#: [Выделить]
  1.             Reference r;
  2.  
  3.             try
  4.             {
  5.                 r = uidoc.Selection.PickObject(
  6.                   ObjectType.Element);
  7.             }
  8.             catch (OperationCanceledException)
  9.             {
  10.                 message = "Cancelled";
  11.                 return Result.Cancelled;
  12.             }
  13.  
  14.             var e = doc.GetElement(r.ElementId);
  15.  
  16.             Stopwatch sw = Stopwatch.StartNew();
  17.  
  18.             foreach (Parameter parameter in e.Parameters)
  19.             {
  20.                 try
  21.                 {
  22.                     DisplayUnitType dut =
  23.                         parameter.DisplayUnitType;
  24.  
  25.                     Debug.Print(dut.ToString());
  26.                 }
  27.                 catch (Autodesk.Revit.Exceptions
  28.                   .InvalidOperationException)
  29.                 {
  30.                 }
  31.             }
  32.             sw.Stop();
  33.  
  34.             TaskDialog.Show("Benchmark result",
  35.               sw.Elapsed.ToString());

Запускам, выбираем элемент. Результат – 0,41 секунды.

Затем, с использованием моего метода-расширения.

Код - C#: [Выделить]
  1.             Reference r;
  2.  
  3.             try
  4.             {
  5.                 r = uidoc.Selection.PickObject(
  6.                   ObjectType.Element);
  7.             }
  8.             catch (OperationCanceledException)
  9.             {
  10.  
  11.                 message = "Canceled";
  12.                 return Result.Cancelled;
  13.             }
  14.  
  15.             var e = doc.GetElement(r.ElementId);
  16.  
  17.             Stopwatch sw = Stopwatch.StartNew();
  18.  
  19.             foreach (Parameter parameter in e.Parameters)
  20.             {
  21.                 if (parameter.HasDisplayUnitType())
  22.                 {
  23.                     DisplayUnitType dut =
  24.                         parameter.DisplayUnitType;
  25.  
  26.                     Debug.Print(dut.ToString());
  27.                 }
  28.             }
  29.             sw.Stop();
  30.  
  31.             TaskDialog.Show("Benchmark result",
  32.               sw.Elapsed.ToString());

Результат – 0,029 секунды на том же самом элементе!

Вы можете сказать, что 0,41 секунды это совсем немного и пользователь даже не заметит этой разницы. Конечно, разница практически не ощущается, когда вы обрабатываете только один элемент. Но, стоит заметить, что перехват исключения работает в 15 раз медленнее. 

Обработка 100 элементов займет 41 секунду при использовании блока try…catch. С новым методом – 3 секунды. Согласитесь, разница получается уже значительной.

 

Автор: Виктор Чекалин
Автор перевода: Виктор Чекалин

Обсуждение: http://adn-cis.org/forum/index.php?topic=195

Опубликовано 31.08.2013
Отредактировано 31.08.2013 в 21:32:09