Найти объект под заданной точкой

Автор Тема: Найти объект под заданной точкой  (Прочитано 18132 раз)

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

Тема содержит сообщение с Решением. Нажмите здесь чтобы посмотреть его.

Оффлайн avcАвтор темы

  • ADN Club
  • *****
  • Сообщений: 822
  • Карма: 166
    • Мои плагины к Автокаду
Задача такая: Надо разобраться на какой объект указывает MLeader. Т.е. что собственно видно во вьюпорте в той точке, куда пользователь тыкнул стрелку выноски. Напрашивается решение «в лоб»: создать луч по оси Z текущего вида в этой известной точке и перебрать ВСЕ объекты чертежа на IntersectWith с этим лучом. Но чертеж будет большим; много блоков, которые надо рекурсивно взрывать… Это катастрофа для производительности. И у меня возникла мысль, что ведь автокад мгновенно выдает объект, когда пользователь кликает по экрану и даже динамически подсвечивает все, что попадается под указателем. Нет ли какого-то эффективного API для аналогичного выбора объекта по точке? Может GetDBObjectIds с каким-то хитрым фильтром?
Заранее благодарен за любые подсказки.

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

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Re: Найти объект под заданной точкой
« Ответ #1 : 20-10-2015, 23:39:35 »
То есть, нужно определить объект модели по точке, заданной в листе на отображении этого объекта в видовом экране?

Оффлайн avcАвтор темы

  • ADN Club
  • *****
  • Сообщений: 822
  • Карма: 166
    • Мои плагины к Автокаду
Re: Найти объект под заданной точкой
« Ответ #2 : 20-10-2015, 23:48:25 »
Да, можно и так сформулировать. Но MLeader может находится и в пространстве модели. Так что просто задана точка, могу найти настройки текущего вида (не важно модель или вьюпорт), надо как-то найти первый видимый объект "под" этой точкой.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Найти объект под заданной точкой
« Ответ #3 : 21-10-2015, 00:10:05 »
avc
Приветствую на форуме.
1) Для того, чтобы выполнялся "быстрый поиск" точка должна быть в видимой области на экране.
2) Для выбора объектов можно воспользоваться Editor.SelectCrossingWindow и задать точки с небольшим смещением влево-вниз и вправо-вверх от заданной точки.
3) Возможно тебе поможет этот код: http://www.theswamp.org/index.php?topic=22963.msg345716#msg345716
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Дима_

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Re: Найти объект под заданной точкой
« Ответ #4 : 21-10-2015, 09:14:16 »
перебрать ВСЕ объекты чертежа на IntersectWith с этим лучом. Но чертеж будет большим; много блоков, которые надо рекурсивно взрывать… Это катастрофа для производительности.
Чтоб это не было катастрофой - читайте соседнею тему - http://adn-cis.org/forum/index.php?topic=2826.0, ключевое слово Rtree.

Оффлайн avcАвтор темы

  • ADN Club
  • *****
  • Сообщений: 822
  • Карма: 166
    • Мои плагины к Автокаду
Re: Найти объект под заданной точкой
« Ответ #5 : 21-10-2015, 11:12:36 »
Даже не ожидал столь оперативной помощи :)
Александр Ривилис, спасибо! Editor.SelectCrossingWindow - это похоже на то, что я искал. Буду пробовать. Если не получится - попробую пошаманить с external вызовами.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Найти объект под заданной точкой
« Ответ #6 : 21-10-2015, 11:15:50 »
Даже не ожидал столь оперативной помощи :)
Здесь всегда так. Не удивляйся.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн avcАвтор темы

  • ADN Club
  • *****
  • Сообщений: 822
  • Карма: 166
    • Мои плагины к Автокаду
Re: Найти объект под заданной точкой
« Ответ #7 : 22-10-2015, 10:32:04 »
Я попробовал метод №3, но получил EntryPointNotFoundException с сообщением "Unable to find an entry point named 'acedNEntSelPEx' in DLL 'acad.exe'"
Недокументированная функция уже удалена из автокада? Или проблемы с версией/разрядностью? Я пробую на Acad 2016 x64

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Найти объект под заданной точкой
« Ответ #8 : 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
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн avcАвтор темы

  • ADN Club
  • *****
  • Сообщений: 822
  • Карма: 166
    • Мои плагины к Автокаду
Re: Найти объект под заданной точкой
« Ответ #9 : 22-10-2015, 10:42:28 »
А не подскажите, можно как-то в C# подлинковывать разные dll во время исполнения в зависимости от текущей версии Автокада?

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Найти объект под заданной точкой
« Ответ #10 : 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
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Отмечено как Решение Александр Ривилис 02-11-2015, 00:27:52

Оффлайн avcАвтор темы

  • ADN Club
  • *****
  • Сообщений: 822
  • Карма: 166
    • Мои плагины к Автокаду
Re: Найти объект под заданной точкой
« Ответ #11 : 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.  
« Последнее редактирование: 02-11-2015, 12:39:02 от avc »

Оффлайн avcАвтор темы

  • ADN Club
  • *****
  • Сообщений: 822
  • Карма: 166
    • Мои плагины к Автокаду
Re: Найти объект под заданной точкой
« Ответ #12 : 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. }

Оффлайн avcАвтор темы

  • ADN Club
  • *****
  • Сообщений: 822
  • Карма: 166
    • Мои плагины к Автокаду
Re: Найти объект под заданной точкой
« Ответ #13 : 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. }

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Найти объект под заданной точкой
« Ответ #14 : 27-10-2015, 17:15:40 »
Для моей задачи оказалось не удобным, что функция возвращает только один «верхний» объект. А в точке вставки выноски – это всегда сама выноска.
Может проще было бы выноску куда-то сдвинуть на время выбора точки?
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение