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

ADN Club => AutoCAD .NET API => Тема начата: Андрей Бушман от 23-12-2013, 16:22:25

Название: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Андрей Бушман от 23-12-2013, 16:22:25
Доброго времени суток.

- AutoCAD 2009 x64 SP3 Enu
- AutoCAD 2014 x64 SP1 Enu

Сотрудники расчётного отдела нашей компании обратились ко мне с маленькой просьбой: написать команду, которая в текущем пространстве (Model\Layout) будет выбирать все полилинии, которые имеют заданное количество вершин. Задачка простая, однако в ходе её решения натолкнулся на неприятный баг: свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace не работает. Тестировал в указанных выше версиях AutoCAD.

Код - C# [Выбрать]
  1. // SpaceEnum.cs
  2. // © Andrey Bushman, 2013
  3.  
  4. namespace Bushman.CAD.DatabaseServices {
  5.         /// <summary>
  6.         /// This enum indicates the current space in the current Database.
  7.         /// </summary>
  8.         public enum SpaceEnum {
  9.                 /// <summary>
  10.                 /// The Model space.
  11.                 /// </summary>
  12.                 Model,
  13.                 /// <summary>
  14.                 /// The Layout space.
  15.                 /// </summary>
  16.                 Layout,
  17.                 /// <summary>
  18.                 /// The Model space through the Layout's viewport.
  19.                 /// </summary>
  20.                 Viewport
  21.         }
  22. }

Код - C# [Выбрать]
  1. // LayoutManagerExtensionMethods.cs
  2. // © Andrey Bushman, 2013
  3.  
  4. //Microsoft
  5. using System;
  6.  
  7. //Autodesk
  8. using cad = Autodesk.AutoCAD.ApplicationServices.Application;
  9. using App = Autodesk.AutoCAD.ApplicationServices;
  10. using Db = Autodesk.AutoCAD.DatabaseServices;
  11. using Ed = Autodesk.AutoCAD.EditorInput;
  12.  
  13. namespace Bushman.CAD.DatabaseServices {
  14.  
  15.         public static class LayoutManagerExtensionMethods {
  16.                 /// <summary>
  17.                 /// This is Extension Method for the <c>Autodesk.AutoCAD.DatabaseServices.LayoutManager</c>
  18.                 /// class. It gets the current space in the current Database.
  19.                 /// </summary>
  20.                 /// <param name="mng">Target <c>Autodesk.AutoCAD.DatabaseServices.LayoutManager</c>
  21.                 /// instance.</param>
  22.                 /// <returns>Returns the SpaceEnum value.</returns>            
  23.                 public static SpaceEnum GetCurrentSpaceEnum(this Db.LayoutManager mng) {
  24.                         Db.Database db = cad.DocumentManager.MdiActiveDocument.Database;
  25.                         Int16 tilemode = (Int16)cad.GetSystemVariable("TILEMODE");
  26.  
  27.                         if (tilemode == 1)
  28.                                 return SpaceEnum.Model;
  29.  
  30.                         Int16 cvport = (Int16)cad.GetSystemVariable("CVPORT");
  31.                         if (cvport == 1)
  32.                                 return SpaceEnum.Layout;
  33.                         else
  34.                                 return SpaceEnum.Viewport;
  35.                 }
  36.  
  37.                 /// <summary>
  38.                 /// This is Extension Method for the <c>Autodesk.AutoCAD.DatabaseServices.LayoutManager</c>
  39.                 /// class. It gets the name of the current space in the current Database.
  40.                 /// </summary>
  41.                 /// <param name="mng">Target <c>Autodesk.AutoCAD.DatabaseServices.LayoutManager</c>
  42.                 /// instance.</param>
  43.                 /// <returns>Returns the name of current space.</returns>
  44.                 public static String GetCurrentSpaceName(this Db.LayoutManager mng) {
  45.                         SpaceEnum space = GetCurrentSpaceEnum(mng);
  46.                         Db.Database db = cad.DocumentManager.MdiActiveDocument.Database;
  47.                         String modelSpaceLocalizedName = String.Empty;
  48.                         using (Db.Transaction tr = db.TransactionManager.StartTransaction()) {
  49.                                 Db.BlockTable bt = tr.GetObject(db.BlockTableId, Db.OpenMode.ForRead) as Db.BlockTable;
  50.                                 Db.BlockTableRecord btr = tr.GetObject(bt[Db.BlockTableRecord.ModelSpace], Db.OpenMode.ForRead)
  51.                                         as Db.BlockTableRecord;
  52.                                 modelSpaceLocalizedName = (tr.GetObject(btr.LayoutId, Db.OpenMode.ForRead) as Db.Layout).LayoutName;
  53.                         }
  54.                         String result = space == SpaceEnum.Viewport ?
  55.                                 "Model" as String : mng.CurrentLayout;
  56.                         return result;
  57.                 }
  58.  
  59.                 /// <summary>
  60.                 /// This is Extension Method for the <c>Autodesk.AutoCAD.DatabaseServices.LayoutManager</c>
  61.                 /// class. It gets the localized name of the Model tab.
  62.                 /// </summary>
  63.                 /// <param name="mng">Target <c>Autodesk.AutoCAD.DatabaseServices.LayoutManager</c>
  64.                 /// instance.</param>
  65.                 /// <returns>Returns the name of current space.</returns>
  66.                 public static String GetModelTabLocalizedName(this Db.LayoutManager mng) {
  67.                         Db.Database db = cad.DocumentManager.MdiActiveDocument.Database;
  68.                         String modelTabLocalizedName = String.Empty;
  69.                         using (Db.Transaction tr = db.TransactionManager.StartTransaction()) {
  70.                                 Db.BlockTable bt = tr.GetObject(db.BlockTableId, Db.OpenMode.ForRead) as Db.BlockTable;
  71.                                 Db.BlockTableRecord btr = tr.GetObject(bt[Db.BlockTableRecord.ModelSpace], Db.OpenMode.ForRead)
  72.                                         as Db.BlockTableRecord;
  73.                                 modelTabLocalizedName = (tr.GetObject(btr.LayoutId, Db.OpenMode.ForRead) as Db.Layout).LayoutName;
  74.                         }
  75.                         return modelTabLocalizedName;
  76.                 }
  77.         }
  78. }

Код - C# [Выбрать]
  1. // SelectionCommands.cs
  2. // Выбор всех полилиний с указанным количеством вершин. © Andrey Bushman, 2013
  3. // Заказ сотрудников расчётного отдела нашей компании.
  4.  
  5. #define SECOND_VARIANT
  6.  
  7. //Microsoft
  8. using System;
  9.  
  10. //Autodesk
  11. using cad = Autodesk.AutoCAD.ApplicationServices.Application;
  12. using App = Autodesk.AutoCAD.ApplicationServices;
  13. using Db = Autodesk.AutoCAD.DatabaseServices;
  14. using Ed = Autodesk.AutoCAD.EditorInput;
  15. using Rtm = Autodesk.AutoCAD.Runtime;
  16.  
  17. // Bushman
  18. using Bushman.CAD.DatabaseServices;
  19.  
  20. [assembly: Rtm.ExtensionApplication(typeof(Bushman.CAD.Commands.SelectionCommands))]
  21. [assembly: Rtm.CommandClass(typeof(Bushman.CAD.Commands.SelectionCommands))]
  22.  
  23. namespace Bushman.CAD.Commands {
  24.  
  25.         public sealed class SelectionCommands : Rtm.IExtensionApplication {
  26.  
  27.                 [Rtm.CommandMethod("PlineSel", Rtm.CommandFlags.Modal)]
  28.                 public void PolylineSelectionViaVertexCount() {
  29.                         App.Document doc = cad.DocumentManager.MdiActiveDocument;
  30.                         Db.Database db = doc.Database;
  31.                         Ed.Editor ed = doc.Editor;
  32.  
  33.                         Ed.PromptIntegerOptions intOpt = new Ed.PromptIntegerOptions("\nКоличество вершин в полилиниях, подлежащих выборке");
  34.                         intOpt.AllowNegative = false;
  35.                         intOpt.AllowNone = false;
  36.                         intOpt.AllowZero = false;
  37.  
  38.                         Ed.PromptIntegerResult intRes = ed.GetInteger(intOpt);
  39.  
  40.                         if (intRes.Status != Ed.PromptStatus.OK) {
  41.                                 ed.WriteMessage("\nНе было выбрано ни одного примитива.\n");
  42.                                 return;
  43.                         }
  44.  
  45. #if !SECOND_VARIANT
  46.                         Boolean rejectObjectsFromNonCurrentSpace = true;
  47.                         Db.TypedValue[] filterList = new Db.TypedValue[2];                     
  48. #else
  49.                         Db.TypedValue[] filterList = new Db.TypedValue[3];
  50.  
  51.                         Db.LayoutManager layMng = Db.LayoutManager.Current;
  52.                         String curLayoutName = layMng.GetCurrentSpaceName();
  53.  
  54.                         filterList[2] = new Db.TypedValue(410, curLayoutName);
  55. #endif
  56.                         filterList[0] = new Db.TypedValue((Int32)Db.DxfCode.Start, "LWPOLYLINE");
  57.                         filterList[1] = new Db.TypedValue(90, intRes.Value);
  58.  
  59.                         Ed.SelectionFilter filter = new Ed.SelectionFilter(filterList);
  60.                         Ed.PromptSelectionOptions selOpt = new Ed.PromptSelectionOptions();
  61.                         selOpt.SingleOnly = false;
  62.  
  63. #if !SECOND_VARIANT
  64.                         selOpt.RejectObjectsFromNonCurrentSpace = rejectObjectsFromNonCurrentSpace;
  65. #endif
  66.  
  67.                         // Нужно в текущем пространстве (Модель\Лист) выделить все примитивы согласно фильтру
  68.                         // так, чтобы отображались в т. ч. и ручки. Причём указанное выделение должно оставаться
  69.                         // активным и по завершению работы кода
  70.                         Ed.PromptSelectionResult selRes = ed.SelectAll(filter);
  71.  
  72.                         if (selRes.Status != Ed.PromptStatus.OK) {
  73.                                 ed.WriteMessage("\nНе было выбрано ни одного примитива.\n");
  74.                                 return;
  75.                         }
  76.  
  77.                         ed.WriteMessage("\nКоличество выбранных примитивов: {0}\n", selRes.Value.Count);
  78.  
  79.                         // Результаты выборки подсвечиваю с отображением "ручек":
  80.                         ed.SetImpliedSelection(selRes.Value.GetObjectIds());
  81.  
  82.                         // Если нужно просто подсветить объекты, без отображения "ручек" то следует
  83.                         // использовать Entity.Highlight(). Чтобы снять такую подсветку, нужно вызывать
  84.                         // метод Entity.Unhighlight()
  85.  
  86.                         // Чтобы очистить текущий набор выборки следует поступать так:
  87.                         // ed.SetImpliedSelection(new Db.ObjectId[0]);
  88.                 }
  89.  
  90.                 #region IExtensionApplication Members
  91.  
  92.                 public void Initialize() {
  93.                         Ed.Editor ed = cad.DocumentManager.MdiActiveDocument.Editor;
  94.                         ed.WriteMessage("\nPlineSel. © Andrey Bushman, 2013\n");
  95.                 }
  96.  
  97.                 public void Terminate() {
  98.                         // throw new NotImplementedException();
  99.                 }
  100.  
  101.                 #endregion
  102.         }
  103. }

К сообщению я прикрепляю чертёж, на котором тестировал код. В пространстве Model находится несколько полилиний, в том числе четыре из них имеют четыре вершины. В пространстве Layout находится ещё две полилинии с четырьмя вершинами.

В выше приведённом коде я показываю два способа фильтрации искомого контента (в файле SelectionCommands.cs обратите внимание на строку 5 и блоки кода в строках 45-55 и 63-65).

Если в обозначенном выше чертеже, находясь в Model или в Layout (в данном случае это не важно), запустить команду PlineSel и в качестве количества вершин указать 4, то в консоли AutoCAD получаем такой результат:

Цитата: AutoCAD Console
Command: PLINESEL
Количество вершин в полилиниях, подлежащих выборке: 4
Количество выбранных элементов: 6

Как видим, в выборку попадают и те полилинии, которые находятся в Layout. И это несмотря на то, что PromptSelectionOptions.RejectObjectsFromNonCurrentSpace установлено в true.

Т. о. получаемый результат неверен.

Для того, чтобы обойти обозначенную выше проблему, раскомментируем в коде строку 5. Теперь в фильтр будет добавлена дополнительная запись с кодом 410. В этом случае я получаю корректные результаты:

Для Model:
Цитата: AutoCAD Console
Command: PLINESEL
Количество вершин в полилиниях, подлежащих выборке: 4
Количество выбранных элементов: 4

Для Layout:
Цитата: AutoCAD Console
Command: PLINESEL
Количество вершин в полилиниях, подлежащих выборке: 4
Количество выбранных элементов: 2

Пользуясь случаем хочу передать привет группе тестирования AutoCAD (ходят слухи, что они всё таки существуют, хотя то же самое, порой, говорят и о Деде Морозе...  ;) ). Желаю этим отличным парням хорошо встретить и провести Новый Год! Того же самого желаю и тем программистам Autodesk, которые много лет назад (как минимум шесть) написали код свойства  PromptSelectionOptions.RejectObjectsFromNonCurrentSpace но поленились проверить его работоспособность. Счастливого вам Нового Года, товарищи программисты! Не икайте там и не кашляйте...
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Дима_ от 24-12-2013, 14:50:29
Может я по диагонали посмотрел - а с чего он (PromptSelectionOptions) должен учитываться в SelectAll(...) ?? Ну создали Вы его с соответствующими опциями - он же ни в каком виде методу не передается и насколько я понимаю служит для передачи методу Editor.GetSelection(...) - которого в Вашем коде я не нашел.
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Александр Ривилис от 24-12-2013, 15:30:11
Вообще-то чтобы после команды выбранные ручки остались подсвеченными нужно чтобы у команды был флаг CommandFlags.Redraw, а чтобы команда могла использовать предварительный выбор еще и CommandFlags.UsePickSet
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Александр Ривилис от 24-12-2013, 15:33:07
Может я по диагонали посмотрел - а с чего он (PromptSelectionOptions) должен учитываться в SelectAll(...)

5 баллов! И я это просмотрел! Хорошо что не успел отправить в ADN DevHelp - пришлось бы краснеть...

Пользуясь случаем хочу передать привет группе тестирования AutoCAD (ходят слухи, что они всё таки существуют, хотя то же самое, порой, говорят и о Деде Морозе...  ;) ).
Андрей. Эта группа не тестирует AutoCAD API. Они тестируют (в меру своих возможностей) сам AutoCAD. Среди них нет программистов - тестировщики это совершенно отдельная профессия.
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Андрей Бушман от 24-12-2013, 16:03:35
Может я по диагонали посмотрел - а с чего он (PromptSelectionOptions) должен учитываться в SelectAll(...) ?? Ну создали Вы его с соответствующими опциями - он же ни в каком виде методу не передается и насколько я понимаю служит для передачи методу Editor.GetSelection(...) - которого в Вашем коде я не нашел.
А млин... :) Точно, слона-то я и не заметил. Дело в том, что изначально у меня в коде был не SelectAll, а GetSelection, дабы юзер сам указывал интересующий его диапазон. Но попробовав, юзеры попросили, чтобы выборка автоматом производилась по всему текущему пространству...

Я выборку поменять-то поменял на SelectAll, но прозевал, что после этого PromptSelectionOptions конечно же становится не использованым и, как следствие, вовсе не нужен... :)

Спасибо.
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Андрей Бушман от 24-12-2013, 16:11:56
Вообще-то чтобы после команды выбранные ручки остались подсвеченными нужно чтобы у команды был флаг CommandFlags.Redraw, а чтобы команда могла использовать предварительный выбор еще и CommandFlags.UsePickSet
Про UsePickSet я помню, но в данной задаче он не нужен. Обозначенный мною выше код подсвечивает ручки и без Redraw, проверял в:
- AutoCAD 2014 SP1 x64 Enu
- AutoCAD 2009 SP3 x64 Enu
- AutoCAD 2009 SP3 x86 Enu

Хотя я конечно же добавлю обозначенный, Вами флаг (https://sites.google.com/site/bushmansnetlaboratory/translate-manual/osnovy-autocad-net-api/opredelenie-komand-i-funkcij-autolisp/opredelenie-komand), спасибо.
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Дмитрий Загорулькин от 24-12-2013, 16:40:55
Вообще-то чтобы после команды выбранные ручки остались подсвеченными нужно чтобы у команды был флаг CommandFlags.Redraw, а чтобы команда могла использовать предварительный выбор еще и CommandFlags.UsePickSet
Практика показывает, что Redraw - это для того, чтобы ручки остались подсвеченными после завершения работы программы, только если нужно обрабатывать предварительный выбор. То есть, при наличии Redraw, UsePickSet не нужен. Сам недавно узнал от тов. bargool с dwg.ru.
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Александр Ривилис от 24-12-2013, 16:54:13
Практика конечно же критерий истины, но лучше всё-таки указывать флаги так, как это полагается делать. Кстати в ObjectARX без этих флагов набор предварительного выбора не подсвечивается/создаётся. И так было всегда! Так что и в AutoCAD .NET API в очередной версии может перестать работать, ибо в принципе это "недокументированное поведение", а по-русски - баг!
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Андрей Бушман от 24-12-2013, 17:12:40
Так что и в AutoCAD .NET API в очередной версии может перестать работать, ибо в принципе это "недокументированное поведение", а по-русски - баг!
На протяжении как минимум шести лет, ибо наблюдается как в 2009-м, так и в 2014-м. Интуитивно ожидаю аналогичного поведения и в 2015-м. Данный случай, конечно не смертелен, но всё же хотелось бы соответствия тому поведению, которое наблюдается в "родном" ARX.
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Андрей Бушман от 25-12-2013, 16:32:27
Поскольку пользователям потребовалось выбирать не только экземпляры Polyline, но и Polyline2d, Polyline3d, то возможность использования SelectionFilter отпадает. Однако в AutoCAD можно реализовать и свой собственный механизм фильтрации, на мой взгляд подчас более гибкий чем тот, который в AutoCAD имеется по умолчанию. Пример одной альтернативной реализации можно посмотреть здесь (http://bushman-andrey.blogspot.ru/2013/12/autocad-linq.html) (механизм реализован в виде набора методов расширений и базируется на технологии LINQ).
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Дима_ от 25-12-2013, 17:25:42
>>возможность использования SelectionFilter отпадает.
*line - выберет все типы + просто линии, а также есть еще маркеры логических гупп "<or" "or>" "<and" "and>" и пр.
p.s. Ихмо для таких задач лисп рулит - строчки 4 наверное получится...
p.p.s. а про вершины забыл - да фильтровать еще все равно придется...
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Александр Ривилис от 25-12-2013, 17:31:07
>>возможность использования SelectionFilter отпадает.
*line - выберет все типы + просто линии, а также есть еще маркеры логических гупп "<or" "or>" "<and" "and>" и пр.
p.s. Ихмо для таких задач лисп рулит - строчки 4 наверное получится...
Дима. А вот в этот раз ты прочитал вопрос по диагонали.  ;) Только у LWPOLYLINE есть свойство в dxf-группе отвечающее за количество вершин. POLYLINE такого свойства не имеет и даже "не знает" сколько у неё вершин.
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Андрей Бушман от 25-12-2013, 17:31:44
>>возможность использования SelectionFilter отпадает.
*line - выберет все типы + просто линии, а также есть еще маркеры логических гупп "<or" "or>" "<and" "and>" и пр.
p.s. Ихмо для таких задач лисп рулит - строчки 4 наверное получится...
Я с интересом посмотрел бы, как у Вас это получится... :)
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Дима_ от 25-12-2013, 17:34:43
да стормозил - с телефона пишу - пока понял уже 2 поста добавилось.
p.s. задача резко возрастает до 7-8 строчек :-)
Название: Re: Не работает свойство PromptSelectionOptions.RejectObjectsFromNonCurrentSpace
Отправлено: Андрей Бушман от 25-12-2013, 17:55:06
POLYLINE такого свойства не имеет и даже "не знает" сколько у неё вершин.

Это дело поправимое...

Код - C# [Выбрать]
  1. // PolylineExtensionMethods.cs. © Andrey Bushman, 2013
  2.  
  3. // Microsoft
  4. using System;
  5. using System.Collections;
  6.  
  7. //Autodesk
  8. using cad = Autodesk.AutoCAD.ApplicationServices.Application;
  9. using App = Autodesk.AutoCAD.ApplicationServices;
  10. using Db = Autodesk.AutoCAD.DatabaseServices;
  11. using Ed = Autodesk.AutoCAD.EditorInput;
  12. using Rtm = Autodesk.AutoCAD.Runtime;
  13.  
  14.  
  15. namespace Bushman.CAD.Tests {
  16.  
  17.         /// <summary>
  18.         /// Extension methods for the Polyline2d and Polyline3d instances.
  19.         /// </summary>
  20.         public static class PolylineExtensionMethods {
  21.  
  22.                 /// <summary>
  23.                 /// Get the number of vertices in the Polyline2d instance.
  24.                 /// </summary>
  25.                 /// <param name="polyline">Target Polyline2d instance.</param>
  26.                 /// <returns>The number of vertices in the Polyline2d instance.</returns>
  27.                 public static Int32 GetNumberOfVertices(this Db.Polyline2d polyline) {
  28.                         return GetItemsCount(polyline);
  29.                 }
  30.  
  31.                 /// <summary>
  32.                 /// Get the number of vertices in the Polyline3d instance.
  33.                 /// </summary>
  34.                 /// <param name="polyline">Target Polyline3d instance.</param>
  35.                 /// <returns>The number of vertices in the Polyline3d instance.</returns>
  36.                 public static Int32 GetNumberOfVertices(this Db.Polyline3d polyline) {
  37.                         return GetItemsCount(polyline);
  38.                 }
  39.  
  40.                 /// <summary>
  41.                 /// Get the items count for the IEnumerable instance.
  42.                 /// </summary>
  43.                 /// <param name="en">Target IEnumerable instance.</param>
  44.                 /// <returns>The items count.</returns>
  45.                 static Int32 GetItemsCount(IEnumerable en) {
  46.                         Int32 count = 0;
  47.                         foreach (var item in en) {
  48.                                 ++count;
  49.                         }
  50.                         return count;
  51.                 }
  52.         }
  53. }

Теперь "знает" :).

Пример использования:

Код - C# [Выбрать]
  1. Db.Polyline2d poly2d = null;
  2. Db.Polyline3d poly3d = null;
  3.  
  4. // here is some initializing of poly2d and poly3d
  5. // ...
  6.  
  7. Int32 count2d = poly2d.GetNumberOfVertices();
  8. Int32 count3d = poly3d.GetNumberOfVertices();
  9.  

Как-то так :).