Статьи > Тестирование статей
Civil 3D API. Получение данных о семействе трубы или колодца.
(1/1)
Дмитрий Загорулькин:
В чём суть проблемы?
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# [Выбрать] ---/// <summary>/// Получение названия семейства для элемента сети/// </summary>/// <param name="part">Элемент сети</param>/// <returns></returns> public static string GetPartFamilyName(this Part part){ try { // Получаем COM-объект для части dynamic partCOM = part.AcadObject; return GetPartFamilyName(part, partCOM.PartFamily); } catch (System.Exception ex) { Debug.WriteLine(ex.Message); Debug.WriteLine(ex.StackTrace); return null; }} /// <summary>/// Получение GUID семейства элементов/// </summary>/// <param name="part">Элемент</param>/// <returns>GUID в виде строки или null</returns> public static string GetPartFamilyGuid(this Part part){ try { dynamic partCOM = part.AcadObject; return partCOM.PartFamily.Guid; } catch (System.Exception ex) { Debug.WriteLine(ex.Message); Debug.WriteLine(ex.StackTrace); return null; }} static string GetPartFamilyName(Part part, dynamic partFamCom){ if (part.PartType == PartType.StructNull) return "Нулевой колодец"; else // Получаем из свойств COM-объекта названия семейства return partFamCom.Name;} Этот способ не обладает недостатками способа "Брутфорс". Нужные данные получаются сразу напрямую из объекта вне зависимости от настроек сети элемента и особенностей построения каталога. Но вы можете обратить внимание на некоторые особенности в том коде, что я привел выше. Блоки try-catch в нём совершенно не просто так! А всё потому, что при использовании в каких-то простых линейных сценариях эти методы работают хорошо. Но если обращаться к ним из событий, методов Overrule и в других подобных "продвинутых" ситуациях - исключение "Неизвестная ошибка", данные не получены. В особо печальных случаях после этого ещё случались глобальные сбои в работе Civil 3D и фатальные ошибки. Долгое время я как мог фильтровал ситуации, в которых было дозволительно вызывать эти методы, а в которых - нет. Ещё, как способ обхода, я применял кэширование - один раз получал данные, сохранял их в статическом словаре-кэше и в следующий раз уже брал данные оттуда, а не вызывал повторно эти опасные методы. Но всё равно раз за разом возникали ситуации, когда без обращения к этим методам было не обойтись и именно в этих ситуациях они сваливались в исключение. Вчера снова я с этим столкнулся и у меня уже кончилось терпение. Ни на что особо не надеясь, но знатно "подгорев", я погрузился в изучение библиотек Civil 3D с помощью рефлексии. И, к моему несказанному удивлению и радости, альтернативное решение всё-таки удалось откопать! Приятным бонусом оказалось то, что этим способом можно получать также некоторые дополнительные свойства элементов!
Решение
В качестве решения просто приведу получившийся код. Я как мог подробно расписал все особенности в комментариях. Проверял его работоспособность в версиях 2014, 2017 и 2019 - работает хорошо. В тестовой команде проверок по-минимуму - специально не усложнял, чтобы можно было сосредоточиться на сути.
--- Код - C# [Выбрать] ---using Autodesk.AutoCAD.ApplicationServices;using Autodesk.AutoCAD.DatabaseServices;using Autodesk.AutoCAD.EditorInput;using Autodesk.AutoCAD.Runtime;using Autodesk.Civil.DatabaseServices;using System;using System.Linq;using System.Reflection; namespace PartTest{ /// <remarks> /// Если название начинается с Pipe - параметр применим только /// к трубам, если со Structure - только к колодцам. /// Если в названии содержится Unknown - это значит что не /// удалось определить назначение параметра или примерно значение /// понятно, но дополнительные исследования для точного определения /// не проводились. /// Если на конце число (1, 2, 3...) - значит, что такой параметр /// неуникален /// </remarks> public enum PartPropertyStringType { PartDescription = 67112965, PartName = 67112979, /// <summary> /// При каждом последующем запросе это значение увеличивается на 1. /// При этом изменяется какой-то объект AeccDbNameTemplatesNode. /// Проверил нумераторы имён труб и колодцев - они не изменяются. /// Изменённое значение сохраняется в чертеже. /// Этот параметр является общим для всех элементов чертежа одного типа: /// отдельное значение для труб, отдельное - для колодцев /// Закомментировал для предотвращения случайных вызовов - т.к. /// непонятно что это, но понятно, что при его использовании /// в чертёж вносятся изменения /// </summary> //UnknownCounter = 67112990, LayerName = 67112992, StyleName = 67112994, ColorName = 67113061, LineTypeName = 67113062, /// <summary> /// AeccDbPipe или AeccDbStructure /// </summary> ClassName = 67113065, /// <summary> /// Обнаруженное значение: Catalog Item /// </summary> PipeUnknown = 67477761, PipeStartStructureName = 67477832, PipeEndStructureName = 67477833, PipeName1 = 67477856, PipeName2 = 67477862, PipeName3 = 67477863, PipeDescription = 67477878, /// <summary> /// Возможные значения: B, Из /// </summary> StructureFirstConnectedPipeFlowDirection = 67481887, /// <summary> /// Возможные значения: С,СВ,В,ЮВ,Ю,ЮЗ,З и СЗ /// </summary> StructureFirstConnectedPipeDirection = 67481889, StructureFirstConnectedPipeShape = 67481891, StructureFirstConnectedPipeMaterial = 67481895, StructureFirstConnectedPipeStartStructureName = 67481897, StructureFirstConnectedPipeEndStructureName = 67481920, StructureFirstConnectedPipeName = 67481921, StructureFirstConnectedPipeSizeName = 67481922, /// <summary> /// Обнаруженное значение: 0 /// </summary> StructureUnknownInt = 67481926, StructureDescription = 67481929, ReferencedAlignmentName = 67485969, ReferencedSurfaceName = 67485991, NetworkName = 67490088, /// <summary> /// Это значение используется внутренними механизмами /// Civil 3D API для получения Part.PartSizeName /// </summary> PartSizeName1 = 67490097, /// <summary> /// Pipe_Domain или Structure_Domain /// </summary> DomainName = 69730504, PartType = 69730505, PartSubType = 69730506, CatalogFamilyName = 69730507, PartSizeName2 = 69730508, CatalogFamilyDescription = 69730509, CatalogGuid = 69730510, FamilyGuid = 69730512, CatalogSizeTableRowGuid = 69730515, // <summary> /// Обнаруженное значение: 2.0 /// </summary> UnknownDouble = 69730520, Material = 69730604, PipeShapeName = 69730704, StructureShapeName = 69730804, /// <summary> /// Обнаруженное значение: Стандарт /// Скорее всего, это то, что в свойствах колодца /// отображается как "Коробка" /// </summary> StructureSizeUnknownParam1 = 69730818, /// <summary> /// Обнаруженное значение: Стандарт /// Скорее всего, это то, что в свойствах колодца /// отображается как "Решётка" /// </summary> StructureSizeUnknownParam2 = 69730819, /// <summary> /// Обнаруженное значение: Стандарт /// Скорее всего, это то, что в свойствах колодца /// отображается как "Покрытие" /// </summary> StructureSizeUnknownParam3 = 69730820, /* * 69731305-69731323(возможно и далее - если добавить в * каталог ещё параметров) - уникальные параметры трубы, в том * числе и нестандартные */ } public class TestProtected { #region Вспомогательные объекты рефлексии static MethodInfo _method; static MethodInfo GetMethod() { if (_method is null) { Type partType = typeof(Part); MethodInfo[] methods = partType.GetMethods (BindingFlags.NonPublic | BindingFlags.Instance); _method = methods.Single (item => item.Name.Equals("InnerGetPropertyStringValue", StringComparison.OrdinalIgnoreCase) && item.GetParameters()[0].ParameterType.Equals(typeof(uint))); } return _method; } #endregion /// <summary> /// Метод получения значения строкового параметра для элемента сети /// </summary> /// <param name="part">Элемент сети, открытый на чтение</param> /// <param name="type">Тип параметра - перечисление</param> /// <returns> /// Строковое значение параметра или *ERROR*, /// если такой параметр не задан у элемента /// </returns> /// <example> /// string familyName = GetPartPropertyString /// (part, PartPropertyStringType.CatalogFamilyName); /// </example> public static string GetPartPropertyString (Part part, PartPropertyStringType type) { uint id = (uint)type; try { return (string)GetMethod()?.Invoke(part, new object[] { id }); } catch { return "*ERROR*"; } } /// <summary> /// Команда для тестирования /// </summary> [CommandMethod("TestPartGetCatalogProps")] public void TestPartCmdRun() { Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; ObjectId partId = ed.GetEntity("\nSelect part: ").ObjectId; if (!partId.IsValid) return; PartPropertyStringType[] stringTypes = Enum .GetValues(typeof(PartPropertyStringType)) .Cast<PartPropertyStringType>() .ToArray(); using (Part part = partId.Open(OpenMode.ForRead) as Part) { ed.WriteMessage("\nPart: {0}, domain: {1}", part.Name, part.Domain); foreach (PartPropertyStringType stringType in stringTypes) { ed.WriteMessage("\n\t\t{0}: {1}", stringType, GetPartPropertyString(part, stringType)); } } } /// <summary> /// Отдельная тестовая команда для проверки поведения непонятного счётчика /// </summary> //[CommandMethod("TestPartGetUnknownInt")] //public void IntTestRun() //{ // Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; // ObjectId partId = ed.GetEntity("\nSelect part: ").ObjectId; // if (!partId.IsValid) return; // using (Part part = partId.Open(OpenMode.ForRead) as Part) // { // ed.WriteMessage("\nPart: {0}, domain: {1}", part.Name, part.Domain); // ed.WriteMessage("\n\t\t{0}: {1}", // "UnknownCounter", GetPartPropertyString // (part, PartPropertyStringType.UnknownCounter)); // } //} }}
Nutson:
Cтолкнулся вот с чем. Civil 2020 в документации так же нет свойств PartFamilyName, PartFamilyId соответственно интелисенс тоже не предлагает такой вариант Part.PartFamilyName.
Однако при просмотре через Snoop Civil 3D эти свойства у объекта видятся) так же эти свойства можно увидеть при отладке кода. Получилось через binding привязать это свойство к TextBlock, так как интелисенс не ловит в разметке xaml.
Во общем пока решил так получать значение:
--- Код - C# [Выбрать] --- var partFamilyName = part.GetType().GetProperty("PartFamilyName").GetValue(part);
Навигация
Перейти к полной версии