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

08/05/2014

Как определить, находится ли точка внутри зоны

В Revit существуют три типа пространственных объектов:

  • Помещение
  • Зона
  • Пространство

В Revit API эти объекты представлены классами Room, Area и Space соответственно, которые наследуются от базового класса SpatialElement.

Классы Room и Space имеют методы для определения, находится ли заданная точка внутри помещения или пространства. IsPointInRoom и IsPointInSpace.

Мне же понадобилось определить, находится ли заданная точка внутри зоны. А метода IsPointInArea, к сожалению, не оказалось. Пришлось реализовывать самостоятельно.

Первым делом нужно было найти алгоритм, по которому можно определить, находится ли точка в заданном пространстве. К счастью, задача довольно распространенная, а значит готовые алгоритмы уже есть.

Я остановился на методе трассировки луча с учетом числа перечислений.

Суть метода довольна проста. От заданной точки проводится воображаемы луч в любом направлении. Затем, подсчитывается количество пересечений луча, с границами полигона. Если луч пересекает полигон четное количество раз, значит точка находится за пределами границ полигона. В противном случае, точка находится внутри полигона.

 

Для реализации данного алгоритма в Revit нужно проделать следующие шаги

  1. Получить список границ пространственного элемента. SpatialElement .GetBoundarySegments(). Границ может быть несколько, например, если посреди помещения есть колонна или отверстие.
  2. Построить луч от заданной точки. Для простоты, построим луч параллельно оси X.
  3. Для каждой границы получить список сегментов, определяющих границу.
  4. Проверить, пересекается ли луч с данным сегментом.
  5. Посчитать количество пересечений

 

На самом деле, данный алгоритм подходит для всех пространственных элементов Revit, поэтому не будем ограничиваться только зоной, а реализуем этот алгоритм для базового класса SpatialElement.

Я часто применяю методы-расширения. Создадим его и в этот раз. Полный код метода для определения нахождения точки внутри пространства:

Код - C#: [Выделить]
  1.     public static class SpatialElementExternsion
  2.     {
  3.         public static bool IsPointInsideSpatialElement(this SpatialElement spatialElement, XYZ point)
  4.         {
  5.             // определим месторасположение пространственного элемента
  6.             var location = spatialElement.Location as LocationPoint;
  7.             if (location == null)
  8.                 return false;
  9.  
  10.             // Если точка лежит ниже уровня пространственного элемента, то возвращаем False;
  11.             if (point.Z < location.Point.Z)
  12.                 return false;
  13.  
  14.             // берем границы объекта
  15.             var boudnaries = spatialElement.GetBoundarySegments(new SpatialElementBoundaryOptions());
  16.  
  17.             // Создадим луч
  18.             Line line = Line.CreateBound(point, new XYZ(10000, 0, 0));
  19.           
  20.             // переменная для подсчета количества пересечений
  21.             int intersectionCount = 0;
  22.            
  23.             foreach (var boundary in boudnaries)
  24.             {
  25.                 foreach (var segment in boundary)
  26.                 {
  27.                     // проверяем, пересекается ли луч с сегментом границы
  28.                     var intersectResult = line.Intersect(segment.Curve);
  29.                     if (intersectResult == SetComparisonResult.Overlap)
  30.                         intersectionCount++;
  31.                 }
  32.             }
  33.  
  34.             // возвращаем true, если количество пересечений нечетное, т.е. остаток от деления на 2 равен 1
  35.             return intersectionCount%2 == 1;        }
  36.     }

На входе нужно задать точку типа XYZ. Для определения пересечения двух кривых используется встроенный метод Curve.Intersect().

Для тестирования метода напишем небольшую команду. Для визуализации, нарисуем также линию, которую мы используем для определения пересечения.

Полный код команды:

Код - C#: [Выделить]
  1.     [Transaction(TransactionMode.Manual)]
  2.     public class Command : IExternalCommand
  3.     {
  4.         public Result Execute(
  5.           ExternalCommandData commandData,
  6.           ref string message,
  7.           ElementSet elements)
  8.         {
  9.             UIApplication uiapp = commandData.Application;
  10.             UIDocument uidoc = uiapp.ActiveUIDocument;
  11.             Application app = uiapp.Application;
  12.             Document doc = uidoc.Document;
  13.  
  14.  
  15.             Reference r;
  16.  
  17.             try
  18.             {
  19.                 r = uidoc.Selection.PickObject(ObjectType.Element, new RoomSelectionFilter(), "Выберите помещение");
  20.             }
  21.             catch (OperationCanceledException)
  22.             {
  23.                 return Result.Cancelled;           
  24.             }
  25.  
  26.             var room = doc.GetElement(r.ElementId) as Room;
  27.  
  28.             List<Curve> polygonCurves = new List<Curve>();
  29.  
  30.             var boundariesSegments = room.GetBoundarySegments(new SpatialElementBoundaryOptions());
  31.  
  32.             foreach (var boundariesSegment in boundariesSegments)
  33.             {
  34.                 foreach (var boundarySegment in boundariesSegment)
  35.                 {
  36.                     var curve = boundarySegment.Curve;
  37.                     polygonCurves.Add(curve);
  38.                 }
  39.             }
  40.  
  41.             var point = uidoc.Selection.PickPoint("Выберите точку");
  42.             
  43.  
  44.             using (var t = new Transaction(doc, "Создание луча"))
  45.             {
  46.                 t.Start();
  47.  
  48.                 Curve line = Line.CreateBound(point, new XYZ(100000, 0, 0));
  49.  
  50.                 Plane plane = new Plane(XYZ.BasisZ, XYZ.Zero);
  51.  
  52.  
  53.                 SketchPlane sketchPlane = SketchPlane.Create(doc, plane);
  54.  
  55.                 ModelLine modelLine = doc.Create.NewModelCurve(line, sketchPlane) as ModelLine;
  56.  
  57.                 t.Commit();
  58.  
  59.                 var result = room.IsPointInsideSpatialElement(point);
  60.  
  61.                 TaskDialog.Show("Проверка", string.Format("Точкаа {0} комнаты {1}", result ? "внутри" : "снаружи", room.Number));
  62.             }
  63.  
  64.             return Result.Succeeded;
  65.         }
  66.     }
  67.  
  68.     internal class RoomSelectionFilter : ISelectionFilter
  69.     {
  70.         public bool AllowElement(Element elem)
  71.         {
  72.             return elem is Room;
  73.         }
  74.  
  75.         public bool AllowReference(Reference reference, XYZ position)
  76.         {
  77.             throw new NotImplementedException();
  78.         }
  79.     }

Для тестирования команды необходимо создать помещение произвольной фигуры.

Затем, запустить команду, выбрать помещение и точку. В результате будет выведено сообщение с результатом.

Пример результата представлен на скриншотах ниже:

 

 

Как я уже говорил, данный метод можно применять для всех дочерних классов SpatialElement.

Автор: Виктор Чекалин
Автор перевода: Виктор Чекалин

Обсуждение: http://adn-cis.org/forum/index.php?topic=725

Опубликовано 08.05.2014
Отредактировано 12.05.2014 в 11:00:04