Civil 3D API. Получение данных о семействе трубы или колодца.

Автор Тема: Civil 3D API. Получение данных о семействе трубы или колодца.  (Прочитано 6086 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 735
В чём суть проблемы?
Civil 3D .NET API предоставляет крайне скудный набор инструментов для получения данных о типоразмере и семействе конкретного отдельного элемента сети. Есть, пожалуй, только одно свойство для получения названия типоразмера: Part.PartSizeName. Для получения данных о семействе - нет вообще ничего!

Предыстория.
Впервые с проблемой определения семейства для труб и колодцев я столкнулся практически сразу же, как начал пробовать писать приложения для Civil 3D. И на протяжении всей моей деятельности, эта проблема раз за разом всплывает, преподнося неприятные сюрпризы. В её решении я прошёл уже через два варианта.

Вариант 1. Брутфорс.
Сперва я использовал метод поиска нужного семейства в списке элементов сети примерно по такому алгоритму:
1. определял название типоразмера элемента и его сеть
2. в свойствах сети искал назначенный список элементов
3. в списке элементов проходил перебором по семействам и их типоразмерам и искал совпадение названия типоразмера семейства с названием типоразмера элемента.
4. когда находил - значит, это искомое семейство.
Довольно быстро обнаружились проблемы такого подхода. Во-первых, список элементов сети - свойство изменяемое и легко переназначаемое. То есть, нет гарантии, что семейство элемента сети присутствует в текущем списке элементов сети. Во-вторых, как показала практика, названия типоразмеров в разных семействах могут совпадать! И тогда такой способ давал некорректный результат. Когда я столкнулся с этими проблемами, пришлось искать другой способ. И вскоре он был найден!

Вариант 2. COM API
Как оказалось, Civil 3D COM API в этом плане гораздо более развитое! У COM-объекта элемента сети есть свойство PartFamily (семейство) и PartSize (типоразмер). Поначалу, приходилось подключать COM-библиотеки AutoCAD и Civil 3D и работать с их типами, что существенно снижало удобство обслуживания кода. Впоследствии эта задача сильно упростилась благодаря применению типа dynamic. И на протяжении нескольких лет именно в таком виде вспомогательные методы получения данных о семействе элемента поселились у меня во вспомогательной библиотеке:
Код - C# [Выбрать]
  1. /// <summary>
  2. /// Получение названия семейства для элемента сети
  3. /// </summary>
  4. /// <param name="part">Элемент сети</param>
  5. /// <returns></returns>        
  6. public static string GetPartFamilyName(this Part part)
  7. {
  8.     try
  9.     {
  10.         // Получаем COM-объект для части
  11.         dynamic partCOM = part.AcadObject;
  12.         return GetPartFamilyName(part, partCOM.PartFamily);
  13.     }
  14.     catch (System.Exception ex)
  15.     {
  16.         Debug.WriteLine(ex.Message);
  17.         Debug.WriteLine(ex.StackTrace);
  18.         return null;
  19.     }
  20. }
  21.  
  22. /// <summary>
  23. /// Получение GUID семейства элементов
  24. /// </summary>
  25. /// <param name="part">Элемент</param>
  26. /// <returns>GUID в виде строки или null</returns>    
  27. public static string GetPartFamilyGuid(this Part part)
  28. {
  29.     try
  30.     {
  31.         dynamic partCOM = part.AcadObject;
  32.         return partCOM.PartFamily.Guid;
  33.     }
  34.     catch (System.Exception ex)
  35.     {
  36.         Debug.WriteLine(ex.Message);
  37.         Debug.WriteLine(ex.StackTrace);
  38.         return null;
  39.     }
  40. }
  41.  
  42. static string GetPartFamilyName(Part part, dynamic partFamCom)
  43. {
  44.     if (part.PartType == PartType.StructNull)
  45.         return "Нулевой колодец";
  46.     else
  47.         // Получаем из свойств COM-объекта названия семейства
  48.         return partFamCom.Name;
  49. }
  50.  
Этот способ не обладает недостатками способа "Брутфорс". Нужные данные получаются сразу напрямую из объекта вне зависимости от настроек сети элемента и особенностей построения каталога. Но вы можете обратить внимание на некоторые особенности в том коде, что я привел выше. Блоки try-catch в нём совершенно не просто так! А всё потому, что при использовании в каких-то простых линейных сценариях эти методы работают хорошо. Но если обращаться к ним из событий, методов Overrule и в других подобных "продвинутых" ситуациях - исключение "Неизвестная ошибка", данные не получены. В особо печальных случаях после этого ещё случались глобальные сбои в работе Civil 3D и фатальные ошибки. Долгое время я как мог фильтровал ситуации, в которых было дозволительно вызывать эти методы, а в которых - нет. Ещё, как способ обхода, я применял кэширование - один раз получал данные, сохранял их в статическом словаре-кэше и в следующий раз уже брал данные оттуда, а не вызывал повторно эти опасные методы. Но всё равно раз за разом возникали ситуации, когда без обращения к этим методам было не обойтись и именно в этих ситуациях они сваливались в исключение. Вчера снова я с этим столкнулся и у меня уже кончилось терпение. Ни на что особо не надеясь, но знатно "подгорев", я погрузился в изучение библиотек Civil 3D с помощью рефлексии. И, к моему несказанному удивлению и радости, альтернативное решение всё-таки удалось откопать! Приятным бонусом оказалось то, что этим способом можно получать также некоторые дополнительные свойства элементов!

Решение
В качестве решения просто приведу получившийся код. Я как мог подробно расписал все особенности в комментариях. Проверял его работоспособность в версиях 2014, 2017 и 2019 - работает хорошо. В тестовой команде проверок по-минимуму - специально не усложнял, чтобы можно было сосредоточиться на сути.
Код - C# [Выбрать]
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.Civil.DatabaseServices;
  6. using System;
  7. using System.Linq;
  8. using System.Reflection;
  9.  
  10. namespace PartTest
  11. {    
  12.     /// <remarks>
  13.     /// Если название начинается с Pipe - параметр применим только
  14.     /// к трубам, если со Structure - только к колодцам.
  15.     /// Если в названии содержится Unknown - это значит что не
  16.     /// удалось определить назначение параметра или примерно значение
  17.     /// понятно, но дополнительные исследования для точного определения
  18.     /// не проводились.
  19.     /// Если на конце число (1, 2, 3...) - значит, что такой параметр
  20.     /// неуникален
  21.     /// </remarks>
  22.     public enum PartPropertyStringType
  23.     {
  24.         PartDescription = 67112965,
  25.         PartName = 67112979,
  26.         /// <summary>
  27.         /// При каждом последующем запросе это значение увеличивается на 1.
  28.         /// При этом изменяется какой-то объект AeccDbNameTemplatesNode.
  29.         /// Проверил нумераторы имён труб и колодцев - они не изменяются.
  30.         /// Изменённое значение сохраняется в чертеже.
  31.         /// Этот параметр является общим для всех элементов чертежа одного типа:
  32.         /// отдельное значение для труб, отдельное - для колодцев
  33.         /// Закомментировал для предотвращения случайных вызовов - т.к.
  34.         /// непонятно что это, но понятно, что при его использовании
  35.         /// в чертёж вносятся изменения
  36.         /// </summary>
  37.         //UnknownCounter = 67112990,
  38.         LayerName = 67112992,
  39.         StyleName = 67112994,        
  40.         ColorName = 67113061,      
  41.         LineTypeName = 67113062,
  42.         /// <summary>
  43.         /// AeccDbPipe или AeccDbStructure
  44.         /// </summary>
  45.         ClassName = 67113065,
  46.         /// <summary>
  47.         /// Обнаруженное значение: Catalog Item
  48.         /// </summary>
  49.         PipeUnknown = 67477761,        
  50.         PipeStartStructureName = 67477832,        
  51.         PipeEndStructureName = 67477833,        
  52.         PipeName1 = 67477856,    
  53.         PipeName2 = 67477862,        
  54.         PipeName3 = 67477863,      
  55.         PipeDescription = 67477878,
  56.         /// <summary>
  57.         /// Возможные значения: B, Из
  58.         /// </summary>
  59.         StructureFirstConnectedPipeFlowDirection = 67481887,
  60.         /// <summary>
  61.         /// Возможные значения: С,СВ,В,ЮВ,Ю,ЮЗ,З и СЗ
  62.         /// </summary>
  63.         StructureFirstConnectedPipeDirection = 67481889,
  64.         StructureFirstConnectedPipeShape = 67481891,
  65.         StructureFirstConnectedPipeMaterial = 67481895,
  66.         StructureFirstConnectedPipeStartStructureName = 67481897,      
  67.         StructureFirstConnectedPipeEndStructureName = 67481920,
  68.         StructureFirstConnectedPipeName = 67481921,
  69.         StructureFirstConnectedPipeSizeName = 67481922,
  70.         /// <summary>
  71.         /// Обнаруженное значение: 0
  72.         /// </summary>
  73.         StructureUnknownInt = 67481926,
  74.         StructureDescription = 67481929,
  75.         ReferencedAlignmentName = 67485969,
  76.         ReferencedSurfaceName = 67485991,        
  77.         NetworkName = 67490088,
  78.         /// <summary>
  79.         /// Это значение используется внутренними механизмами
  80.         /// Civil 3D API для получения Part.PartSizeName  
  81.         /// </summary>
  82.         PartSizeName1 = 67490097,
  83.         /// <summary>
  84.         /// Pipe_Domain или Structure_Domain
  85.         /// </summary>
  86.         DomainName = 69730504,
  87.         PartType = 69730505,
  88.         PartSubType = 69730506,
  89.         CatalogFamilyName = 69730507,
  90.         PartSizeName2 = 69730508,
  91.         CatalogFamilyDescription = 69730509,
  92.         CatalogGuid = 69730510,
  93.         FamilyGuid = 69730512,
  94.         CatalogSizeTableRowGuid = 69730515,
  95.         // <summary>
  96.         /// Обнаруженное значение: 2.0
  97.         /// </summary>
  98.         UnknownDouble = 69730520,
  99.         Material = 69730604,
  100.         PipeShapeName = 69730704,
  101.         StructureShapeName = 69730804,
  102.         /// <summary>
  103.         /// Обнаруженное значение: Стандарт
  104.         /// Скорее всего, это то, что в свойствах колодца
  105.         /// отображается как "Коробка"
  106.         /// </summary>
  107.         StructureSizeUnknownParam1 = 69730818,
  108.         /// <summary>
  109.         /// Обнаруженное значение: Стандарт
  110.         /// Скорее всего, это то, что в свойствах колодца
  111.         /// отображается как "Решётка"
  112.         /// </summary>
  113.         StructureSizeUnknownParam2 = 69730819,
  114.         /// <summary>
  115.         /// Обнаруженное значение: Стандарт
  116.         /// Скорее всего, это то, что в свойствах колодца
  117.         /// отображается как "Покрытие"
  118.         /// </summary>
  119.         StructureSizeUnknownParam3 = 69730820,
  120.         /*
  121.          * 69731305-69731323(возможно и далее - если добавить в
  122.          * каталог ещё параметров) - уникальные параметры трубы, в том
  123.          * числе и нестандартные
  124.          */
  125.     }
  126.  
  127.     public class TestProtected
  128.     {
  129.         #region Вспомогательные объекты рефлексии
  130.  
  131.         static MethodInfo _method;
  132.  
  133.         static MethodInfo GetMethod()
  134.         {
  135.             if (_method is null)
  136.             {
  137.                 Type partType = typeof(Part);
  138.                 MethodInfo[] methods = partType.GetMethods
  139.                     (BindingFlags.NonPublic | BindingFlags.Instance);
  140.                 _method = methods.Single
  141.                     (item => item.Name.Equals("InnerGetPropertyStringValue",
  142.                     StringComparison.OrdinalIgnoreCase)
  143.                     && item.GetParameters()[0].ParameterType.Equals(typeof(uint)));
  144.             }
  145.             return _method;
  146.         }
  147.  
  148.         #endregion
  149.  
  150.         /// <summary>
  151.         /// Метод получения значения строкового параметра для элемента сети
  152.         /// </summary>
  153.         /// <param name="part">Элемент сети, открытый на чтение</param>
  154.         /// <param name="type">Тип параметра - перечисление</param>
  155.         /// <returns>
  156.         /// Строковое значение параметра или *ERROR*,
  157.         /// если такой параметр не задан у элемента
  158.         /// </returns>
  159.         /// <example>
  160.         /// string familyName = GetPartPropertyString
  161.         ///     (part, PartPropertyStringType.CatalogFamilyName);
  162.         /// </example>
  163.         public static string GetPartPropertyString
  164.             (Part part, PartPropertyStringType type)
  165.         {
  166.             uint id = (uint)type;
  167.             try
  168.             {
  169.                 return (string)GetMethod()?.Invoke(part, new object[] { id });
  170.             }
  171.             catch
  172.             {
  173.                 return "*ERROR*";
  174.             }
  175.         }        
  176.  
  177.         /// <summary>
  178.         /// Команда для тестирования
  179.         /// </summary>
  180.         [CommandMethod("TestPartGetCatalogProps")]
  181.         public void TestPartCmdRun()
  182.         {
  183.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  184.             ObjectId partId = ed.GetEntity("\nSelect part: ").ObjectId;
  185.             if (!partId.IsValid) return;
  186.  
  187.             PartPropertyStringType[] stringTypes = Enum
  188.                 .GetValues(typeof(PartPropertyStringType))
  189.                 .Cast<PartPropertyStringType>()
  190.                 .ToArray();
  191.  
  192.             using (Part part = partId.Open(OpenMode.ForRead) as Part)
  193.             {
  194.                 ed.WriteMessage("\nPart: {0}, domain: {1}", part.Name, part.Domain);
  195.                 foreach (PartPropertyStringType stringType in stringTypes)
  196.                 {
  197.                     ed.WriteMessage("\n\t\t{0}: {1}",
  198.                         stringType, GetPartPropertyString(part, stringType));
  199.                 }            
  200.             }
  201.         }
  202.  
  203.         /// <summary>
  204.         /// Отдельная тестовая команда для проверки поведения непонятного счётчика
  205.         /// </summary>
  206.         //[CommandMethod("TestPartGetUnknownInt")]
  207.         //public void IntTestRun()
  208.         //{
  209.         //    Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  210.         //    ObjectId partId = ed.GetEntity("\nSelect part: ").ObjectId;
  211.         //    if (!partId.IsValid) return;
  212.  
  213.         //    using (Part part = partId.Open(OpenMode.ForRead) as Part)
  214.         //    {
  215.         //        ed.WriteMessage("\nPart: {0}, domain: {1}", part.Name, part.Domain);
  216.  
  217.         //        ed.WriteMessage("\n\t\t{0}: {1}",
  218.         //            "UnknownCounter", GetPartPropertyString
  219.         //            (part, PartPropertyStringType.UnknownCounter));
  220.  
  221.         //    }
  222.         //}
  223.     }
  224. }
  225.  
  226.  

Оффлайн Nutson

  • ADN OPEN
  • Сообщений: 43
  • Карма: 6
Cтолкнулся вот с чем. Civil 2020 в документации так же нет свойств PartFamilyName, PartFamilyId соответственно интелисенс тоже не предлагает такой вариант Part.PartFamilyName.
Однако при просмотре через Snoop Civil 3D эти свойства у объекта видятся) так же эти свойства можно увидеть при отладке кода. Получилось через binding привязать это свойство к TextBlock, так как интелисенс не ловит в разметке xaml.

Во общем пока решил так получать значение:
Код - C# [Выбрать]
  1.                     var partFamilyName = part.GetType().GetProperty("PartFamilyName").GetValue(part);
  2.  
  3.