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

04/11/2013

Определение элемента, образующего сегмент границы комнаты

Сегодня мы рассмотрим, как определить элемент сегмента границы комнаты с помощью класса ReferenceIntersector, проведя воображаемую прямую изнутри комнаты в элемент, граничащий с комнатой.

Может быть и не следовало реализовывать подобную функциональность самостоятельно, так как класс BoundarySegment уже содержит свойство Element, который по идее должен возвратить элемент, образующий заданный сегмент.

Однако, к сожалению, свойство BoundarySegment. Element при определенных обстоятельствах может возвратить null. Мы рассмотрим способ, как обойти это ограничение. Этот способ можно использовать в том случае, если свойство Element равно null.

Вот несколько примеров, где мы обсуждали как получать границы комнат

Мы также обсуждали несколько примеров как найти соседние элементы с помощью воображаемой линии:

 

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

В предыдущем примере мы создавали луч из комнаты через середину каждого сегмента, чтобы найти соседние комнаты. Этот же пример будет чуть короче, и мы будем определять элемент, который находится между двумя сегментами.

Рудольф Хонке(Rudolf Honke) из компании Mensch und Maschine acadGraph поднял эту проблему и обнаружил, что не стоит всегда полагаться на свойство Element класса BoundarySegment.

Вопрос: мне нужно определить каждый элемент вдоль границы комнаты.

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

 

Отмеченный сегмент не возвращает элемента, образующего данный сегмент. Свойство BoundarySegment. Element возвращает null.

Я протестировал на нескольких файлах. Везде возвращается null. Похоже это стандартное поведение Revit API.

Ответ: Давайте попробуем найти другой способ, как получить то что вы хотите.

Например, что если использовать такой алгоритм. Вы обрабатываете список с сегментами границы комнаты. Если предшествующий и последующий сегмент принадлежит одному и тому же элементу, а у текущего сегмента свойство Element равно null, то мы предполагаем, что и у этого сегмента тот же самый образующий элемент?

Ответ: Нет. Данный алгоритм не подойдет, так как могут быть ситуации, когда предшествующий и следующий сегмент образуются различными элементами:

 

Ответ: Хорошо. Вижу, что на вашем примере, предложенный мной алгоритм работать не будет.

Еще один вариант. Очень легко реализуемый:

  1. Для каждого сегмента, у которого свойство Element равно null
  2. Определить направление поверхности сегмента, смотрящее в комнату
  3. Определить точку в комнате, которая находится совсем чуть-чуть от середины сегмента границы комнаты.
  4. Провести воображаемую линию между этой точкой и серединой сегмента границы.
  5. Проверить, что за элемент находится на середине сегмента. Если там нет элемента, то что-то  тут не так.
  6. Этот элемент и является элементом, который вы ищете, т.е. элемент, образующий сегмент.

Ответ: Спасибо за подсказку. Я уже почти реализовал подобную идею.

С помощью вашего совета, я получил то что нужно.

Теперь нет необходимости использовать соседние сегменты.

Реализация метода GetElementByRay

Рудольф также предоставил свою реализацию алгоритма.

Код - C#: [Выделить]
  1.  /// <summary>
  2.   /// Return direction turning 90 degrees
  3.   /// left from given input vector.
  4.   /// </summary>
  5.   public XYZ GetLeftDirection( XYZ direction )
  6.   {
  7.     double x = -direction.Y;
  8.     double y = direction.X;
  9.     double z = direction.Z;
  10.     return new XYZ( x, y, z );
  11.   }
  12.  
  13.   /// <summary>
  14.   /// Return direction turning 90 degrees
  15.   /// right from given input vector.
  16.   /// </summary>
  17.   public XYZ GetRightDirection( XYZ direction )
  18.   {
  19.     return GetLeftDirection( direction.Negate() );
  20.   }
  21.  
  22.   /// <summary>
  23.   /// Return the neighbouring BIM element generating
  24.   /// the given room boundary curve c, assuming it
  25.   /// is oriented counter-clockwise around the room
  26.   /// if part of an interior loop, and vice versa.
  27.   /// </summary>
  28.   public Element GetElementByRay(
  29.     UIApplication app,
  30.     Document doc,
  31.     View3D view3d,
  32.     Curve c )
  33.   {
  34.     Element boundaryElement = null;
  35.  
  36.     // Tolerances
  37.  
  38.     const double minTolerance = 0.00000001;
  39.     const double maxTolerance = 0.01;
  40.  
  41.     // Height of ray above room level:
  42.     // ray starts from one foot above room level
  43.  
  44.     const double elevation = 1;
  45.  
  46.     // Ray starts not directly from the room border
  47.     // but from a point offset slightly into it.
  48.  
  49.     const double stepInRoom = 0.1;
  50.  
  51.     // We could use Line.Direction if Curve c is a
  52.     // Line, but since c also might be an Arc, we
  53.     // calculate direction like this:
  54.  
  55.     XYZ lineDirection
  56.       = ( c.GetEndPoint( 1 ) - c.GetEndPoint( 0 ) )
  57.         .Normalize();
  58.  
  59.     XYZ upDir = elevation * XYZ.BasisZ;
  60.  
  61.     // Assume that the room is on the left side of
  62.     // the room boundary curve and wall on the right.
  63.     // This is valid for both outer and inner room
  64.     // boundaries (outer are counter-clockwise, inner
  65.     // are clockwise). Start point is slightly inside
  66.     // the room, one foot above room level.
  67.  
  68.     XYZ toRoomVec = stepInRoom * GetLeftDirection(
  69.       lineDirection );
  70.  
  71.     XYZ pointBottomInRoom = c.Evaluate( 0.5, true )
  72.       + toRoomVec;
  73.  
  74.     XYZ startPoint = pointBottomInRoom + upDir;
  75.  
  76.     // We are searching for walls only
  77.  
  78.     ElementFilter wallFilter
  79.       = new ElementCategoryFilter(
  80.         BuiltInCategory.OST_Walls );
  81.  
  82.     ReferenceIntersector intersector
  83.       = new ReferenceIntersector( wallFilter,
  84.         FindReferenceTarget.Element, view3d );
  85.  
  86.     // We don't want to find elements in linked files
  87.  
  88.     intersector.FindReferencesInRevitLinks = false;
  89.  
  90.     XYZ toWallDir = GetRightDirection(
  91.       lineDirection );
  92.  
  93.     ReferenceWithContext context = intersector
  94.       .FindNearest( startPoint, toWallDir );
  95.  
  96.     Reference closestReference = null;
  97.  
  98.     if( context != null )
  99.     {
  100.       if( ( context.Proximity > minTolerance )
  101.         && ( context.Proximity < maxTolerance
  102.           + stepInRoom ) )
  103.       {
  104.         closestReference = context.GetReference();
  105.  
  106.         if( closestReference != null )
  107.         {
  108.           boundaryElement = doc.GetElement(
  109.             closestReference );
  110.         }
  111.       }
  112.     }
  113.     return boundaryElement;
  114.   }
  115.  

Реализация команды GetBoundarySegmentElement

Я также реализовал небольшую надстройку, которую назвал GetBoundarySegmentElement и добавил туда команду, с помощью которой можно выполнить метод, предоставленный Рудольфом.

Код - C#: [Выделить]
  1. public Result Execute(
  2.   ExternalCommandData commandData,
  3.   ref string message,
  4.   ElementSet elements )
  5. {
  6.   UIApplication uiapp = commandData.Application;
  7.   UIDocument uidoc = uiapp.ActiveUIDocument;
  8.   Application app = uiapp.Application;
  9.   Document doc = uidoc.Document;
  10.   Selection sel = uidoc.Selection;
  11.  
  12.   List<Room> rooms = new List<Room>(
  13.     sel.Elements.Cast<Room>() );
  14.  
  15.   if( 1 != rooms.Count )
  16.   {
  17.     message = "Please select exactly one room.";
  18.  
  19.     return Result.Failed;
  20.   }
  21.  
  22.   View3D view3d
  23.     = new FilteredElementCollector( doc )
  24.       .OfClass( typeof( View3D ) )
  25.       .Cast<View3D>()
  26.       .FirstOrDefault<View3D>(
  27.         e => e.Name.Equals( "{3D}" ) );
  28.  
  29.   if( null == view3d )
  30.   {
  31.     message = "No 3D view named '{3D}' found.";
  32.  
  33.     return Result.Failed;
  34.   }
  35.  
  36.   foreach( Room room in rooms )
  37.   {
  38.     IList<IList<BoundarySegment>> loops
  39.       = room.GetBoundarySegments(
  40.         new SpatialElementBoundaryOptions() );
  41.  
  42.     int n = loops.Count;
  43.  
  44.     Debug.Print(
  45.       "Room {0} has {1} loop{2}{3}",
  46.       room.Name, n, PluralSuffix( n ),
  47.       DotOrColon( n ) );
  48.  
  49.     int i = 0;
  50.  
  51.     foreach( IList<BoundarySegment> loop in loops )
  52.     {
  53.       n = loop.Count;
  54.  
  55.       Debug.Print(
  56.         "  Loop {0} has {1} segment{2}{3}",
  57.         i++, n, PluralSuffix( n ),
  58.         DotOrColon( n ) );
  59.  
  60.       int j = 0;
  61.  
  62.       foreach( BoundarySegment seg in loop )
  63.       {
  64.         Element e = seg.Element;
  65.  
  66.         string s = "Element property";
  67.  
  68.         if( null == e )
  69.         {
  70.           s = "GetElementByRay";
  71.  
  72.           e = GetElementByRay( uiapp, doc, view3d,
  73.             seg.Curve );
  74.         }
  75.  
  76.         Debug.Print(
  77.           "    Segment {0}: {1} element {2} returned by {3}",
  78.           j++, CurveString( seg.Curve ),
  79.           ElementDescription( e ), s );
  80.       }
  81.     }
  82.   }
  83.   return Result.Succeeded;
  84. }

Как всегда, 45% кода это валидация входных параметров, другие 45% это вывод результатов, и только 10% строчек делают что-то по настоящему полезное.

Вот результат выполнения команды на примере модели, предствленной выше:

Room Test 1 has 1 loop:

  Loop 0 has 8 segments:

    Segment 0: line (-13.95,23.02,0) --> (-21.99,23.02,0)

      wall 326988 MW 24.0 WD 12.0 returned by Element property

    Segment 1: line (-21.99,23.02,0) --> (-21.99,13.04,0)

      wall 327085 MW 24.0 WD 12.0 returned by Element property

    Segment 2: line (-21.99,13.04,0) --> (-3.81,13.04,0)

      wall 327055 MW 24.0 WD 12.0 returned by Element property

    Segment 3: line (-3.81,13.04,0) --> (-3.81,23.02,0)

      wall 327018 MW 24.0 WD 12.0 returned by Element property

    Segment 4: line (-3.81,23.02,0) --> (-12.97,23.02,0)

      wall 326988 MW 24.0 WD 12.0 returned by Element property

    Segment 5: line (-12.97,23.02,0) --> (-12.97,17.44,0)

      wall 327196 STB 30.0 returned by Element property

    Segment 6: line (-12.97,17.44,0) --> (-13.95,17.44,0)

      wall 327196 STB 30.0 returned by GetElementByRay

    Segment 7: line (-13.95,17.44,0) --> (-13.95,23.02,0)

      wall 327196 STB 30.0 returned by Element property

Заметьте, что свойство Element равно null для 6 сегмента и в этом случае используется метод GetElementByRay.

SpatialElementBoundaryLocation

Отклик: когда я читал черновик поста, я заметил следующее:

Код - C#: [Выделить]
  1.  foreach( Room room in rooms )
  2.   {
  3.     IList<IList<BoundarySegment>> loops
  4.       = room.GetBoundarySegments(
  5.         new SpatialElementBoundaryOptions() );

Я хочу заметить, что мы должны явно указать в классе SpatialElementBoundaryOptions какое положение нам нужно использовать для определения границы. Нам нужна финишная поверхность.

 

Вот правильный код:

Код - C#: [Выделить]
  1.  SpatialElementBoundaryOptions opt
  2.     = new SpatialElementBoundaryOptions();
  3.  
  4.   opt.SpatialElementBoundaryLocation
  5.     = SpatialElementBoundaryLocation.Finish;
  6.  
  7.   IList<IList<BoundarySegment>> loops
  8.     = room.GetBoundarySegments( opt );

Конечно, может быть при инициализации класса SpatialElementBoundaryOptions свойство SpatialElementBoundaryLocation уже равно Finish. Но я не нашел никакого упоминания об этом в файле справки.

В противном случае, граница комнаты может находиться внутри граничащих элементов и результат может быть иным.

Граница может выглядеть вот так (выделено красным)

 

Ответ: Я добавил предлагаемые надстройки и получил такой результат:

Room Test 1 has 1 loop:

  Loop 0 has 8 segments:

    Segment 0: line (-13.95,23.02,0) --> (-21.99,23.02,0)

      wall 326988 MW 24.0 WD 12.0 returned by Element property

    Segment 1: line (-21.99,23.02,0) --> (-21.99,13.04,0)

      wall 327085 MW 24.0 WD 12.0 returned by Element property

    Segment 2: line (-21.99,13.04,0) --> (-3.81,13.04,0)

      wall 327055 MW 24.0 WD 12.0 returned by Element property

    Segment 3: line (-3.81,13.04,0) --> (-3.81,23.02,0)

      wall 327018 MW 24.0 WD 12.0 returned by Element property

    Segment 4: line (-3.81,23.02,0) --> (-12.97,23.02,0)

      wall 326988 MW 24.0 WD 12.0 returned by Element property

    Segment 5: line (-12.97,23.02,0) --> (-12.97,17.44,0)

      wall 327196 STB 30.0 returned by Element property

    Segment 6: line (-12.97,17.44,0) --> (-13.95,17.44,0)

      wall 327196 STB 30.0 returned by GetElementByRay

    Segment 7: line (-13.95,17.44,0) --> (-13.95,23.02,0)

      wall 327196 STB 30.0 returned by Element property

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

Ответ: Да. Finish является значением по умолчанию.

После создания объекта SpatialElementBoundaryOptions я вижу вот такую картину в отладчике:

 

Код проекта

Увидеть целиком рабочую надстройку и самостоятельно ее попробовать вы можете скачав исходники.

Большое спасибо Рудольфу за то что заметил проблему и представил реаилизацию ее решения.

Источник: http://thebuildingcoder.typepad.com/blog/2013/10/determining-a-room-boundary-segment-generating-element.html

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

Опубликовано 04.11.2013
Отредактировано 04.11.2013 в 12:34:01