SelectCrossingPolygon. Как отловить некорректный контур?

Автор Тема: SelectCrossingPolygon. Как отловить некорректный контур?  (Прочитано 26494 раз)

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

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

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

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Здравствуйте!
Столкнулся с неприятной ошибкой в работе моей программы, связанной с тем, что в метод PromptSelectionResult передаются точки, образующие контур с самопересечениями.
Результатом работы метода редактора SelectCrossingPolygon является объект PromptSelectionResult с двумя свойствами - статус и значение. Если в метод передать некорректный контур или в указанном контуре не окажется нужных объектов, то возвращаемое значение метода будет одинаково: статус - Error, значение - null. Мне нужно отловить ситуацию, когда в метод передается некорректный контур. Как это можно сделать? Может быть есть какой-то способ проверки контура на корректность для передачи в метод?
Спасибо.

Для примера:
Код - C# [Выбрать]
  1. [CommandMethod("SelectCrossingTest")]
  2.         public void SelectCrossingTest()
  3.         {
  4.             Document adoc = Application.DocumentManager.MdiActiveDocument;
  5.             Database db = adoc.Database;
  6.             Editor ed = adoc.Editor;
  7.  
  8.             PromptEntityOptions entOpt = new PromptEntityOptions("\nSelect pline: ");
  9.             entOpt.SetRejectMessage("Is NOT pline!");
  10.             entOpt.AddAllowedClass(typeof(Polyline), true);
  11.  
  12.             PromptEntityResult entRes = ed.GetEntity(entOpt);
  13.             if (entRes.Status != PromptStatus.OK) return;
  14.  
  15.             ObjectId plineId = entRes.ObjectId;
  16.  
  17.             Point3dCollection gripPts = new Point3dCollection();
  18.  
  19.             using (Transaction tr = db.TransactionManager.StartTransaction())
  20.             {
  21.                 Polyline pline = tr.GetObject(plineId, OpenMode.ForRead) as Polyline;                
  22.                 pline.GetGripPoints(gripPts, new IntegerCollection(), new IntegerCollection());
  23.                 tr.Commit();
  24.             }
  25.  
  26.             PromptSelectionResult selRes = ed.SelectCrossingPolygon(gripPts, new SelectionFilter(new TypedValue[]{ new TypedValue((int)DxfCode.Start, "CIRCLE")}));
  27.             ed.WriteMessage("\nStatus: {0}", selRes.Status);
  28.         }

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

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Может быть есть какой-то способ проверки контура на корректность для передачи в метод?
Если имеется в виду "некорректный" == "самопересекающийся", то очевидным ответом будет проверить на самопересечение. Например, каждый сегмент, заданный точкой начала и конца, проверяется на пересечение со всеми остальными (кроме двух соседних). Если пересечение есть, и это не касание в конечных точках, то контур "некорректный".
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

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

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Да кто же его знает, что еще может подразумеваться под "некорректным". Сегодня вот такой нашли вариант - самопересекающийся, завтра может быть еще какой-то найдем :). Странно, что исключение в методе при этом не возникает.
Я подумал, что, возможно, есть какой-то стандартный способ проверки. Как-то же метод понимает, что контур ему не подходит?
И непонятно, в каком случае касание допустимое, а в каком - недопустимое. Проверил на 4-х разных контурах:
1-й - статус "ОК".
2-й - статус "Error" т.к. нет объектов внутри.
3-й - статус "Error" т.к. некорректный контур
4-й - статус "ОК", хотя самопересечение есть...
« Последнее редактирование: 27-01-2014, 18:36:36 от Загорулькин Дмитрий »

Отмечено как Решение Дмитрий Загорулькин 08-10-2014, 14:43:36

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

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Я вспомнил, что когда-то много лет назад использовал в ObjectARX для проверка на самопересечение класс AcDbMPolygon. Решил немного поэкспериментировать и получил такой код:

Код - C# [Выбрать]
  1. using System;
  2. using Autodesk.AutoCAD.Runtime;
  3. using Autodesk.AutoCAD.ApplicationServices;
  4. using Autodesk.AutoCAD.DatabaseServices;
  5. using Autodesk.AutoCAD.Geometry;
  6. using Autodesk.AutoCAD.EditorInput;
  7.  
  8. [assembly: CommandClass(typeof(Rivilis.TestSelfCrossing))]
  9.  
  10. namespace Rivilis
  11. {
  12.   public class TestSelfCrossing
  13.   {
  14.     [CommandMethod("TestCross", CommandFlags.Modal)]
  15.     public void TestCross()
  16.     {
  17.       Document adoc = Application.DocumentManager.MdiActiveDocument;
  18.       Database db = adoc.Database;
  19.       Editor ed = adoc.Editor;
  20.       PromptEntityOptions entOpt = new PromptEntityOptions("\nВыберите полилинию: ");
  21.       entOpt.SetRejectMessage("Это не полилиния!");
  22.       entOpt.AddAllowedClass(typeof(Polyline), true);
  23.       PromptEntityResult entRes = ed.GetEntity(entOpt);
  24.       if (entRes.Status != PromptStatus.OK) return;
  25.       Point3dCollection gripPts = new Point3dCollection();
  26.       MPolygon mpoly = new MPolygon();
  27.       bool isValidBoundary = false;
  28.       using (Transaction tr = db.TransactionManager.StartTransaction()) {
  29.         Polyline pline = tr.GetObject(entRes.ObjectId, OpenMode.ForRead) as Polyline;
  30.         pline.GetGripPoints(gripPts, new IntegerCollection(), new IntegerCollection());
  31.         try {
  32.           mpoly.AppendLoopFromBoundary(pline, true, Tolerance.Global.EqualPoint);
  33.           if (mpoly.NumMPolygonLoops != 0) {
  34.             isValidBoundary = true;
  35.           }
  36.         } catch {}
  37.         tr.Commit();
  38.       }
  39.       mpoly.Dispose();
  40.       if (isValidBoundary) {
  41.         PromptSelectionResult selRes = ed.SelectCrossingPolygon(gripPts, new SelectionFilter(new TypedValue[] { new TypedValue((int)DxfCode.Start, "CIRCLE") }));
  42.         ed.WriteMessage("\nStatus: {0}", selRes.Status);
  43.       } else {
  44.         ed.WriteMessage("\nНеподходящая граница!");
  45.       }
  46.     }
  47.   }
  48. }
  49.  

Несколько оговорок:
1. Необходимо к проекту подключить сборку AcMPolygonMGD.dll и установить для неё Copy Local в False
2. Я проверял только в AutoCAD 2013, но эта сборка есть во всех версиях начиная как минимум с AutoCAD 2008
3. Возможно она не пропустит контура, которые SelectCrossingPolygon и пропустила бы...
4. Работать будет только если UCS == WCS и полилиния лежит в плоскости WCS (это касается и твоего кода тоже).
5. Полилиния должна быть видна целиком на экране.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

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

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Интересное решение. Покрутил его и так и эдак - работает как надо! Буду думать, как его пристроить в программу. Спасибо!

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

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
У класса MPolygon есть еще много интересных возможностей. Например, проверка попадания точки внутрь контура.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

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

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Так это вообще великолепно! Можно будет вообще избавиться от использования SelectCrossingPolygon! Вычислить ключевые точки объектов и проверить, находятся ли они внутри полигона.

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

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Ну если тебе достаточно проверять попадание точек параллелепипеда (Entity.GeometricExtents) внутрь контура - то это было бы оптимально. Лично я очень не люблю SelectCrossingPolygon и аналогичные методы, которые требуют видимость объектов на экране.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

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

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Думаю, мне достаточно будет даже попадание в контур только одной характерной точки объекта. Если при этом не выберутся объекты, частично попадающие в контур, но с характерной точкой вне его, то это будет даже плюсом.

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

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Я правильно понимаю, что если IntegerCollection, которую возвращает метод IsPointInsideMPolygon, будет непустая, то точка попала в контур? "Метод тыка" подтверждает это, но может быть как-то по другому надо?
Если это так, то заметил интересную особенность - если контур накладывается сам на себя, то точка внутри него не опознается. Например, для контура как на картинке, в зонах 1 и 4 точка распознается как точка внутри контура, а в зонах 2 и 3 - как вне его.
Код - C# [Выбрать]
  1. namespace Rivilis
  2. {
  3.     public class TestSelfCrossing
  4.     {
  5.         [CommandMethod("TestMPoly", CommandFlags.Modal)]
  6.         public void TestCross()
  7.         {
  8.             Document adoc = Application.DocumentManager.MdiActiveDocument;
  9.             Database db = adoc.Database;
  10.             Editor ed = adoc.Editor;
  11.             PromptEntityOptions entOpt = new PromptEntityOptions("\nВыберите полилинию: ");
  12.             entOpt.SetRejectMessage("Это не полилиния!");
  13.             entOpt.AddAllowedClass(typeof(Polyline), true);
  14.             PromptEntityResult entRes = ed.GetEntity(entOpt);
  15.             if (entRes.Status != PromptStatus.OK) return;
  16.  
  17.             Point2dCollection polyPts = new Point2dCollection();
  18.             using (Transaction tr = db.TransactionManager.StartTransaction())
  19.             {
  20.                 Polyline pline = tr.GetObject(entRes.ObjectId, OpenMode.ForRead) as Polyline;
  21.                 Point3dCollection gripPts = new Point3dCollection();
  22.                 pline.GetGripPoints(gripPts, new IntegerCollection(), new IntegerCollection());
  23.  
  24.                 foreach (Point3d pt in gripPts)
  25.                 {
  26.                     polyPts.Add(new Point2d(pt.X, pt.Y));
  27.                 }
  28.  
  29.                 tr.Commit();
  30.             }
  31.  
  32.             PromptPointResult ptRes = ed.GetPoint("\nУкажите точку: ");
  33.  
  34.             if (ptRes.Status != PromptStatus.OK) return;
  35.  
  36.             Point2dCollection testPrs = new Point2dCollection();
  37.             testPrs.Add(new Point2d(ptRes.Value.X, ptRes.Value.Y));
  38.  
  39.             bool[] testRes = PolygonPointsTest(polyPts, testPrs);
  40.  
  41.  
  42.             Application.ShowAlertDialog(testRes[0] ? "Точка внутри контура" : "Точка вне контура");
  43.         }
  44.  
  45.  
  46.  
  47.         public static bool[] PolygonPointsTest(Point2dCollection polygonPts, Point2dCollection testPts)
  48.         {
  49.             List<bool> testRes = new List<bool>();
  50.             using (MPolygon mPoly = new MPolygon())
  51.             {
  52.                 MPolygonLoop mpLoop = new MPolygonLoop();
  53.                 foreach (Point2d pt in polygonPts)
  54.                 {
  55.                     mpLoop.Add(new BulgeVertex(pt, 0.0));
  56.                 }
  57.  
  58.                 // Для того чтобы получить нормальный контур, нужно добавить первую точку
  59.                 mpLoop.Add(new BulgeVertex(polygonPts[0], 0.0));
  60.  
  61.                 mPoly.AppendMPolygonLoop(mpLoop, false, Tolerance.Global.EqualPoint);
  62.  
  63.                 foreach (Point2d pt in testPts)
  64.                 {
  65.                     IntegerCollection intCol = mPoly.IsPointInsideMPolygon(new Point3d(pt.X, pt.Y, 0.0), Tolerance.Global.EqualPoint);
  66.                     testRes.Add(intCol.Count > 0);
  67.                 }
  68.             }
  69.  
  70.             return testRes.ToArray();
  71.         }
  72.     }
  73. }
« Последнее редактирование: 06-02-2014, 17:03:21 от Загорулькин Дмитрий »

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

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Да.
Если это так, то заметил интересную особенность - если контур накладывается сам на себя, то точка внутри него не опознается. Например, для контура как на картинке, в зонах 1 и 4 точка распознается как точка внутри контура, а в зонах 2 и 3 - как вне его.
Это же у тебя самопересекающийся контур. Ты мог его сразу отсечь как некорректный.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

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

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Да, поначалу была идея сообщать пользователю о некорректном контуре и прерывать программу. Но беда в том, что автокад позволяет создавать самопересекающиеся контуры видовых экранов (я ищу объекты определенного типа, отображающиеся в отдельном видовом экране). Если стандартные возможности позволяют это, а программа будет говорить "нельзя", то это в некотором роде ущемление свободы действий пользователя.
Сейчас поэкспериментировал с ВЭ - логика отображения совпадает с логикой обнаружения точек внутри MPolygon. Так что, благодаря Вам, идеальный способ обнаружения нужных объектов найден! Спасибо еще раз!

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

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Но беда в том, что автокад позволяет создавать самопересекающиеся контуры видовых экранов
Это извращение! Я думаю что раз твоя программа анализирует контуры видовых экранов, то неплохо было бы и сообщать пользователю об этих "извращениях". Скорее всего он это сделал случайно и даже понятия об этом не имеет.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

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

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Ну то что я нарисовал - конечно извращение жуткое :)
А вот скрин участка чертежа, на котором споткнулась моя программа и из-за чего и пошел сыр-бор. Согласен, что это тоже не очень грамотно. Насчет предупреждения подумаю, может быть добавлю.

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

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Не можешь спросить у автора этого чертежа в чем глубинный смысл "елозить" по одному месту (наложение сегментов полилилинии в левом нижнем углу чертежа)? Это небрежность или тут какой-то глубинный смысл?
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение