Статьи > Тестирование статей

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);  



Навигация

[0] Главная страница сообщений

Перейти к полной версии