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

ADN Club => AutoCAD .NET API => Тема начата: avc от 20-10-2015, 23:26:48

Название: Найти объект под заданной точкой
Отправлено: avc от 20-10-2015, 23:26:48
Задача такая: Надо разобраться на какой объект указывает MLeader. Т.е. что собственно видно во вьюпорте в той точке, куда пользователь тыкнул стрелку выноски. Напрашивается решение «в лоб»: создать луч по оси Z текущего вида в этой известной точке и перебрать ВСЕ объекты чертежа на IntersectWith с этим лучом. Но чертеж будет большим; много блоков, которые надо рекурсивно взрывать… Это катастрофа для производительности. И у меня возникла мысль, что ведь автокад мгновенно выдает объект, когда пользователь кликает по экрану и даже динамически подсвечивает все, что попадается под указателем. Нет ли какого-то эффективного API для аналогичного выбора объекта по точке? Может GetDBObjectIds с каким-то хитрым фильтром?
Заранее благодарен за любые подсказки.
Название: Re: Найти объект под заданной точкой
Отправлено: Дмитрий Загорулькин от 20-10-2015, 23:39:35
То есть, нужно определить объект модели по точке, заданной в листе на отображении этого объекта в видовом экране?
Название: Re: Найти объект под заданной точкой
Отправлено: avc от 20-10-2015, 23:48:25
Да, можно и так сформулировать. Но MLeader может находится и в пространстве модели. Так что просто задана точка, могу найти настройки текущего вида (не важно модель или вьюпорт), надо как-то найти первый видимый объект "под" этой точкой.
Название: Re: Найти объект под заданной точкой
Отправлено: Александр Ривилис от 21-10-2015, 00:10:05
avc
Приветствую на форуме.
1) Для того, чтобы выполнялся "быстрый поиск" точка должна быть в видимой области на экране.
2) Для выбора объектов можно воспользоваться Editor.SelectCrossingWindow и задать точки с небольшим смещением влево-вниз и вправо-вверх от заданной точки.
3) Возможно тебе поможет этот код: http://www.theswamp.org/index.php?topic=22963.msg345716#msg345716
Название: Re: Найти объект под заданной точкой
Отправлено: Дима_ от 21-10-2015, 09:14:16
перебрать ВСЕ объекты чертежа на IntersectWith с этим лучом. Но чертеж будет большим; много блоков, которые надо рекурсивно взрывать… Это катастрофа для производительности.
Чтоб это не было катастрофой - читайте соседнею тему - http://adn-cis.org/forum/index.php?topic=2826.0 (http://adn-cis.org/forum/index.php?topic=2826.0), ключевое слово Rtree.
Название: Re: Найти объект под заданной точкой
Отправлено: avc от 21-10-2015, 11:12:36
Даже не ожидал столь оперативной помощи :)
Александр Ривилис, спасибо! Editor.SelectCrossingWindow - это похоже на то, что я искал. Буду пробовать. Если не получится - попробую пошаманить с external вызовами.
Название: Re: Найти объект под заданной точкой
Отправлено: Александр Ривилис от 21-10-2015, 11:15:50
Даже не ожидал столь оперативной помощи :)
Здесь всегда так. Не удивляйся.
Название: Re: Найти объект под заданной точкой
Отправлено: avc от 22-10-2015, 10:32:04
Я попробовал метод №3, но получил EntryPointNotFoundException с сообщением "Unable to find an entry point named 'acedNEntSelPEx' in DLL 'acad.exe'"
Недокументированная функция уже удалена из автокада? Или проблемы с версией/разрядностью? Я пробую на Acad 2016 x64
Название: Re: Найти объект под заданной точкой
Отправлено: Александр Ривилис от 22-10-2015, 10:39:08
Я попробовал метод №3, но получил EntryPointNotFoundException с сообщением "Unable to find an entry point named 'acedNEntSelPEx' in DLL 'acad.exe'"
Недокументированная функция уже удалена из автокада? Или проблемы с версией/разрядностью? Я пробую на Acad 2016 x64
Нет. Но есть пару нюансов, связанных с версией и разрядностью AutoCAD:
1) Вместо "acad.exe" должно быть "accore.dll" во всех версиях AutoCAD начиная с 2013
2)
EntryPoint="?acedNEntSelPEx@@YAHPB_WQAJQANHQAY03NPAPAUresbuf@@IPAH@Z" для AutoCAD x86
EntryPoint="?acedNEntSelPEx@@YAHPEB_WQEA_JQEANHQEAY03NPEAPEAUresbuf@@IPEA_J@Z" для AutoCAD x64
Название: Re: Найти объект под заданной точкой
Отправлено: avc от 22-10-2015, 10:42:28
А не подскажите, можно как-то в C# подлинковывать разные dll во время исполнения в зависимости от текущей версии Автокада?
Название: Re: Найти объект под заданной точкой
Отправлено: Александр Ривилис от 22-10-2015, 11:07:27
А не подскажите, можно как-то в C# подлинковывать разные dll во время исполнения в зависимости от текущей версии Автокада?
О каких dll идёт речь? Если о тех, которые вызываются через P/Invoke, как в случае с acedNEntSelPEx, то нужно просто описать несколько прототипов и вызвать тот, который соответствует разрядности и версии AutoCAD:
1) Если Application.Version.Major > 18 - то версия 2013 или выше
2) Если IntPtr.Size == 4, то x86. В противном случае x64.

P.S.: Это не линковка, а динамическая загрузка и вызов функции. Аналогично LoadLibrary + GetProcAddress в C++

P.S.S.: В качестве примера: http://adn-cis.org/forum/index.php?topic=819.msg3301#msg3301
Название: Re: Найти объект под заданной точкой
Отправлено: avc от 27-10-2015, 16:19:41
Наконец-то я нашел время, отписаться о результатах. Все получилось. Считаю своим долгом опубликовать код. Надеюсь,  кому-нибудь пригодится.
Итак, внятная обертка для функции acedNEntSelPEx получилась такой:
(К сожалению, не знаю назначение некоторых параметров)
Код - C# [Выбрать]
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.InteropServices;
  4. using Autodesk.AutoCAD.ApplicationServices;
  5. using Autodesk.AutoCAD.DatabaseServices;
  6. using Autodesk.AutoCAD.Geometry;
  7. using Autodesk.AutoCAD.Runtime;
  8. using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;
  9.  
  10. namespace AVC
  11. {
  12.   /// <summary>
  13.   /// Обертка для недокументированной функции acedNEntSelPEx
  14.   /// </summary>
  15.   public static class ObjUnderPoint
  16.   {
  17.     static int ver = Application.Version.Major;
  18.  
  19.     /// <summary>
  20.     /// Поиск видимого объекта чертежа под заданной точкой
  21.     /// Точку можно передать или запросить у пользователя
  22.     /// Возвращает только 1 объект (верхний в порядке DrawOrder)
  23.     /// Работает только с  MdiActiveDocument и только на текущем Layout
  24.     /// </summary>
  25.     /// <param name="Prompt">Запрос выбора точки</param>
  26.     /// <param name="Picked">Точка для поиска в UCS (передаваемая методу или возвращаемая от пользователя)</param>
  27.     /// <param name="UsePickedPoint">Использовать переданную точку и ничего не запрашивать у пользователя</param>
  28.     /// <param name="Transform">Какая-то матрица. Может пересчет текущей системы координат в систему координат выбранного объекта?</param>
  29.     /// <param name="TransSpace">Видеть сквозь viewport</param>
  30.     /// <param name="GSMarker">Код SubEntity (Часть солида) Например 5-грань,6-вершина,26-ребро... 0-неделимый объект</param>
  31.     /// <returns>Список найденных объектов. Сначала видимый объект, потом блоки в который он включен, потом viewport (если включен TransSpace) </returns>
  32.     public static List<ObjectId> Find(string Prompt, out Point3d Picked, bool UsePickedPoint, out Matrix3d Transform, bool TransSpace, out int GSMarker)
  33.     {
  34.       List<ObjectId> ret = new List<ObjectId>();
  35.       long[] adsname = { 0, 0 };
  36.       IntPtr resbuf = IntPtr.Zero;
  37.       int result = 0;
  38.       uint transSpaceFlag = (uint)(TransSpace ? 1 : 0);
  39.       int pickflag = UsePickedPoint ? 1 : 0;
  40.  
  41.       if (ver < 19) // AutoCAD 2012 и старее
  42.       {
  43.         if (IntPtr.Size == 4)
  44.           result = acedNEntSelPEx2012x32("", adsname, out Picked, pickflag, out Transform, out resbuf, transSpaceFlag, out GSMarker);
  45.         else
  46.           result = acedNEntSelPEx2012x64("", adsname, out Picked, pickflag, out Transform, out resbuf, transSpaceFlag, out GSMarker);
  47.       }
  48.       else // AutoCAD 2013 и новее
  49.       {
  50.         if (IntPtr.Size == 4)
  51.           result = acedNEntSelPEx2013x32("", adsname, out Picked, pickflag, out Transform, out resbuf, transSpaceFlag, out GSMarker);
  52.         else
  53.           result = acedNEntSelPEx2013x64("", adsname, out Picked, pickflag, out Transform, out resbuf, transSpaceFlag, out GSMarker);
  54.       }
  55.  
  56.       if (result == 5100) // 5100 == OK
  57.       {
  58.         ObjectId id = new ObjectId();
  59.         switch (ver)
  60.         {
  61.           case 17: acdbGetObjectId17(ref id, adsname); break;
  62.           case 18: acdbGetObjectId18(ref id, adsname); break;
  63.           case 19: acdbGetObjectId19(ref id, adsname); break;
  64.           case 20: acdbGetObjectId20(ref id, adsname); break;
  65.           default: throw new NotImplementedException("Комманда не совместима с данной версией автокада");
  66.         }
  67.         if (!id.IsNull) ret.Add(id);
  68.  
  69.         if (resbuf != IntPtr.Zero) // значит попался вьюпорт или блок
  70.         {
  71.           ResultBuffer buffer = (ResultBuffer)DisposableWrapper.Create(typeof(ResultBuffer), resbuf, true);
  72.           foreach (TypedValue v in buffer) // первым в списке будет самый глубоко вложенный блок, последним - вьюпорт
  73.             ret.Add((ObjectId)v.Value);
  74.           buffer.Dispose();
  75.         }
  76.  
  77.  
  78.       }
  79.  
  80.       return ret;
  81.     }
  82.  
  83.     /// <summary>
  84.     /// Поиск видимого объекта чертежа под заданной точкой. Видит "сквозь" вьюпорты
  85.     /// Возвращает только 1 объект (верхний в порядке DrawOrder)
  86.     /// Работает только с MdiActiveDocument и только на текущем листе (Layout). Система координат листа должна быть мировой.
  87.     /// </summary>
  88.     /// <param name="Picked">Точка поиска в UCS</param>
  89.     /// <returns>Список найденных объектов. Сначала видимый объект, потом блоки в который он включен, потом viewport (если включен TransSpace)</returns>
  90.     public static List<ObjectId> Find(Point3d Picked)
  91.     {
  92.       Matrix3d xform;
  93.       int gsmarker = 0;
  94.       // set uTransSpaceFlag to 0, if the current layout is in model space
  95.       bool transSpace = ((Convert.ToInt16(AcadApp.GetSystemVariable("CVPORT")) == 1));  // Paper space
  96.  
  97.       return Find("", out Picked, true, out xform, transSpace, out gsmarker);
  98.     }
  99.  
  100.     #region acdbGetObjectId
  101.  
  102.     [DllImport("acdb17.dll", EntryPoint = "acdbGetObjectId", CallingConvention = CallingConvention.Cdecl)]
  103.     private static extern int acdbGetObjectId17(ref ObjectId objId, long[] name);
  104.  
  105.     [DllImport("acdb18.dll", EntryPoint = "acdbGetObjectId", CallingConvention = CallingConvention.Cdecl)]
  106.     private static extern int acdbGetObjectId18(ref ObjectId objId, long[] name);
  107.  
  108.     [DllImport("acdb19.dll", EntryPoint = "acdbGetObjectId", CallingConvention = CallingConvention.Cdecl)]
  109.     private static extern int acdbGetObjectId19(ref ObjectId objId, long[] name);
  110.  
  111.     [DllImport("acdb20.dll", EntryPoint = "acdbGetObjectId", CallingConvention = CallingConvention.Cdecl)]
  112.     private static extern int acdbGetObjectId20(ref ObjectId objId, long[] name);
  113.  
  114.     #endregion
  115.  
  116.     #region acedNEntSelPEx
  117.  
  118.     [DllImport("acad.exe", EntryPoint = "?acedNEntSelPEx@@YAHPB_WQAJQANHQAY03NPAPAUresbuf@@IPAH@Z", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
  119.     private static extern int acedNEntSelPEx2012x32(string prompt, long[] adsname, out Point3d picked, int pickflag, out Matrix3d transform, out IntPtr resbuf, uint transSpaceFlag, out int gsMarker);
  120.  
  121.     [DllImport("acad.exe", EntryPoint = "?acedNEntSelPEx@@YAHPEB_WQEA_JQEANHQEAY03NPEAPEAUresbuf@@IPEA_J@Z", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
  122.     private static extern int acedNEntSelPEx2012x64(string prompt, long[] adsname, out Point3d picked, int pickflag, out Matrix3d transform, out IntPtr resbuf, uint transSpaceFlag, out int gsMarker);
  123.  
  124.     [DllImport("accore.dll", EntryPoint = "?acedNEntSelPEx@@YAHPB_WQAJQANHQAY03NPAPAUresbuf@@IPAH@Z", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
  125.     private static extern int acedNEntSelPEx2013x32(string prompt, long[] adsname, out Point3d picked, int pickflag, out Matrix3d transform, out IntPtr resbuf, uint transSpaceFlag, out int gsMarker);
  126.  
  127.     [DllImport("accore.dll", EntryPoint = "?acedNEntSelPEx@@YAHPEB_WQEA_JQEANHQEAY03NPEAPEAUresbuf@@IPEA_J@Z", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
  128.     private static extern int acedNEntSelPEx2013x64(string prompt, long[] adsname, out Point3d picked, int pickflag, out Matrix3d transform, out IntPtr resbuf, uint transSpaceFlag, out int gsMarker);
  129.  
  130.     #endregion
  131.  
  132.   }
  133. }
  134.  
  135.  
Название: Re: Найти объект под заданной точкой
Отправлено: avc от 27-10-2015, 16:20:23
Тестовая команда может быть такой:
Код - C# [Выбрать]
  1. [CommandMethod("doit")]
  2. public static void acedNEntSelPExTest()
  3. {
  4.   Document doc = AcadApp.DocumentManager.MdiActiveDocument;
  5.   if (doc == null) return;
  6.   Editor ed = doc.Editor;
  7.   PromptPointResult ppr = ed.GetPoint("Кликните по объекту");
  8.   if (ppr.Status != PromptStatus.OK) return;
  9.   List<ObjectId> ids = ObjUnderPoint.Find(ppr.Value);
  10.   foreach (ObjectId id in ids) ed.WriteMessage("\n" + id.ObjectClass.Name + " #" + id.Handle.ToString());
  11. }
Название: Re: Найти объект под заданной точкой
Отправлено: avc от 27-10-2015, 16:21:38
Для моей задачи оказалось не удобным, что функция возвращает только один «верхний» объект. А в точке вставки выноски – это всегда сама выноска. Пришлось забивать костыль - отключать видимость выноски и вызывать Editor.Regen(), который притормаживает и приводит к морганию экрана.
И еще вылезла проблемка: если найдется изощренный пользователь, который переключит систему координат листа, то объекты во вьюпорте уже не найдутся, как не трансформируй точку. Пришлось назло пользователю возвращать мировую систему координат.

Код - C# [Выбрать]
  1. using (Transaction tr = doc.TransactionManager.StartTransaction())
  2. {
  3.   MLeader ml = tr.GetObject(mLeaderId, OpenMode.ForWrite) as MLeader;
  4.   if (ml.ContentType != ContentType.MTextContent || ml.MText.Contents != "") return;
  5.   Point3d point = ml.GetFirstVertex(0);
  6.   if ((Convert.ToInt16(AcadApp.GetSystemVariable("CVPORT")) != 1))  // Model space
  7.   {
  8.     Matrix3d ucs = ed.CurrentUserCoordinateSystem;
  9.     point = point.TransformBy(ucs.Inverse()); // Пересчет WCS -> UCS
  10.   }
  11.   else // Если пользователя угораздит перенести систему координат на бумаге, то объекты во вьюпорте найти не получится
  12.   {
  13.     ed.CurrentUserCoordinateSystem = Matrix3d.Identity; // вернем WCS для листа
  14.   }
  15.   ml.Visible = false;
  16.   ed.Regen();
  17.  
  18.   List<ObjectId> ids;
  19.   try
  20.   {
  21.     ids = ObjUnderPoint.Find(point);
  22.   }
  23.   finally { ml.Visible = true; }
  24.   if (ids.Count == 0) return;
  25.   using (DBObject obj = tr.GetObject(ids[0], OpenMode.ForRead))
  26.   {
  27.     // Записываем информацию об объекте obj в ml.MText.Contents
  28.     ..
  29.        
  30.   }
  31.   tr.Commit();
  32. }
Название: Re: Найти объект под заданной точкой
Отправлено: Александр Ривилис от 27-10-2015, 17:15:40
Для моей задачи оказалось не удобным, что функция возвращает только один «верхний» объект. А в точке вставки выноски – это всегда сама выноска.
Может проще было бы выноску куда-то сдвинуть на время выбора точки?
Название: Re: Найти объект под заданной точкой
Отправлено: avc от 27-10-2015, 17:21:48
Сдвинуть или отключить - не важно. Заковырка не в этом, а в том, что надо как-то обновить изображение перед вызовом поиска объекта.
Название: Re: Найти объект под заданной точкой
Отправлено: Александр Ривилис от 27-10-2015, 17:31:04
Сдвинуть или отключить - не важно. Заковырка не в этом, а в том, что надо как-то обновить изображение перед вызовом поиска объекта.
Если разделишь транзакцию на две, то не придётся выполнять регенерацию, которая может быть достаточно длительной операцией. По поводу отключить или передвинуть - согласен.
Название: Re: Найти объект под заданной точкой
Отправлено: avc от 27-10-2015, 17:45:18
А подскажете код EntryPoint этой функции (acedNEntSelPEx) для Acad 2012x64 ?
Название: Re: Найти объект под заданной точкой
Отправлено: Александр Ривилис от 27-10-2015, 17:47:22
А подскажете код EntryPoint этой функции (acedNEntSelPEx) для Acad 2012x64 ?
"?acedNEntSelPEx@@YAHPEB_WQEA_JQEANHQEAY03NPEAPEAUresbuf@@IPEA_J@Z"
Название: Re: Найти объект под заданной точкой
Отправлено: avc от 02-11-2015, 15:57:57
Замечу еще одну особенность применения функции acedNEntSelPEx. Она возвращает вовсе не тот объект, который находится точно под заданной точкой. Она выбирает первый попавшийся (в порядке DrawOrder) объект, среди всех объектов попавших в квадрат перекрестья курсора (PickBox). Даже если сработала привязка к точке на одном объекте, на выходе функции будет другой объект. А пользователь скорее всего не заметит, что выбран не тот объект, т.к. они расположены очень близко (pickbox обычно всего 3 пиксела на экране). "Виртуальный" пикбокс учитывается даже в случае, если точка передается в функцию извне.
Исправить этот баг не сложно. Я просто ставлю системную переменную PICKBOX на 0 (AcadApp.SetSystemVariable("PICKBOX", 0)) на время вызова функции, и возвращаю ее обратно по завершении. В try-finally, конечно.
Название: Re: Найти объект под заданной точкой
Отправлено: Александр Ривилис от 02-11-2015, 16:03:43
Она возвращает вовсе не тот объект, который находится точно под заданной точкой. Она выбирает первый попавшийся (в порядке DrawOrder) объект, среди всех объектов попавших в квадрат перекрестья курсора (PickBox).
Точно также работают и acedEntsel (Editor.GetEntity), acedNentsel(p) (Editor.GetNestedEntity). Это стандарт AutoCAD с незапамятных времён...
Название: Re: Найти объект под заданной точкой
Отправлено: aipx от 24-04-2016, 17:22:04
Я начинающий в написании плагинов, не судите строго если вопрос будет сформулирован не верно:
У меня есть модель на которой размещены блоки с указателями их названий мультивыноской, мне нужно создать  SortedDictionary<TKey, TValue> Блок-Название.

С помощью метода указанного в данном посте, я нахожу координаты Point3d мультивыносок, которые я получаю командой -  Mleader.FirstVertex(0) ( я так понимаю координаты мне передаются с  ModelSpace ). При этом хотелось бы получить объекты под всеми мультивыносками модели, что бы это не зависило от того насколько я отмасштабировал модель, а оно ищет только в том участке который виден на экране.  Подскажите хотя бы в каком направлении копать? Я предпологаю, что это связано с видовыми экранами и системами координат, но описать не могу  :(
Название: Re: Найти объект под заданной точкой
Отправлено: Александр Ривилис от 24-04-2016, 17:36:17
aipx
Приветствую на форуме!
Обозначенный алгоритм работает только с примитивами в видимой области экрана. Так что если тебя это не устраивает нужно думать о другом алгоритме.
Название: Re: Найти объект под заданной точкой
Отправлено: Александр Ривилис от 24-04-2016, 17:38:58
Например, отбираешь все свои блоки в модели и все свои мультивыноски в модели. Ну а дальше находишь соответствие их друг другу (опираясь на точки вставки блоков и начало мультивыноски)
Название: Re: Найти объект под заданной точкой
Отправлено: aipx от 24-04-2016, 17:48:08
Александр Ривилис, Очень благодарен за помощь! Вы мне сэкономили кучу времени, на протяжении которого я бы ковырялся еще в этом коде. Буду думать в другом направлении!
Название: Re: Найти объект под заданной точкой
Отправлено: avc от 25-05-2016, 23:22:00
Буду думать в другом направлении!
Необязательно!
Я наконец нашел время решить эту проблему с поиском объектов за экраном и все оказалось проще простого. Перед поиском объектов надо:
1. Для выносок в пространстве бумаги надо выйти из вьюпорта:
Код - C# [Выбрать]
  1.             if (mleader.BlockName != "*Model_Space") { db.TileMode = false;  ed.SwitchToPaperSpace(); }
2. Вывести все объекты на экран
Код - C# [Выбрать]
  1.             ZoomExtents();  ed.UpdateScreen();
ZoomExtents приходится вызывать через маленький танец с бубном
Код - C# [Выбрать]
  1.     public static void ZoomExtents()
  2.     {
  3.       object acadObject = Application.AcadApplication;
  4.       acadObject.GetType().InvokeMember("ZoomExtents", BindingFlags.InvokeMethod, null, acadObject, null);
  5.     }
Все это безусловно костыли. Настройки вида мы пользователю по портим. По хорошему надо писать свой метод поиска объектов в заданной точке, точнее на пересечении осью Z текущего вида. А связывать объекты через XData или как-то еще "не геометрически" - по моему гораздо более  тернистый путь. Придется отлавливать любые модификации этих объектов. И как гарантировать, что эти модификации не произошли на компьютере, где нет нашего плагина?