Определение элемента, образующего сегмент границы комнаты
Сегодня мы рассмотрим, как определить элемент сегмента границы комнаты с помощью класса ReferenceIntersector, проведя воображаемую прямую изнутри комнаты в элемент, граничащий с комнатой.
Может быть и не следовало реализовывать подобную функциональность самостоятельно, так как класс BoundarySegment уже содержит свойство Element, который по идее должен возвратить элемент, образующий заданный сегмент.
Однако, к сожалению, свойство BoundarySegment. Element при определенных обстоятельствах может возвратить null. Мы рассмотрим способ, как обойти это ограничение. Этот способ можно использовать в том случае, если свойство Element равно null.
Вот несколько примеров, где мы обсуждали как получать границы комнат
- Accessing room data
- Graphically display area boundary loops
- Room in area predicate via point in polygon test
- Analysis of Room and Space 3D geometry in the Revit 2012 API news
- New GetBoundarySegments method in the Revit 2013 API news
- Retrieving plan view room boundary polygon loops
- Поиск соседних комнат
Мы также обсуждали несколько примеров как найти соседние элементы с помощью воображаемой линии:
- Identifying wall compound layers and parts
- The ReferenceIntersector class
- Wall footing relationship
- UIView, Windows Coordinates, ReferenceIntersector and My Own Tooltip
- Determining the columns supporting a beam by ray casting
- Space adjacency for heat load calculation
- Поиск соседних комнат
Обратите внимание, что недавняя тема поиск соседних комнат находится в обоих группах и на самом деле очень близка к тому, что мы будем обсуждать сегодня.
В предыдущем примере мы создавали луч из комнаты через середину каждого сегмента, чтобы найти соседние комнаты. Этот же пример будет чуть короче, и мы будем определять элемент, который находится между двумя сегментами.
Рудольф Хонке(Rudolf Honke) из компании Mensch und Maschine acadGraph поднял эту проблему и обнаружил, что не стоит всегда полагаться на свойство Element класса BoundarySegment.
Вопрос: мне нужно определить каждый элемент вдоль границы комнаты.
Однако, я столкнулся с проблемой, когда стена заходит во внутрь комнаты, как в примере ниже.
Отмеченный сегмент не возвращает элемента, образующего данный сегмент. Свойство BoundarySegment. Element возвращает null.
Я протестировал на нескольких файлах. Везде возвращается null. Похоже это стандартное поведение Revit API.
Ответ: Давайте попробуем найти другой способ, как получить то что вы хотите.
Например, что если использовать такой алгоритм. Вы обрабатываете список с сегментами границы комнаты. Если предшествующий и последующий сегмент принадлежит одному и тому же элементу, а у текущего сегмента свойство Element равно null, то мы предполагаем, что и у этого сегмента тот же самый образующий элемент?
Ответ: Нет. Данный алгоритм не подойдет, так как могут быть ситуации, когда предшествующий и следующий сегмент образуются различными элементами:
Ответ: Хорошо. Вижу, что на вашем примере, предложенный мной алгоритм работать не будет.
Еще один вариант. Очень легко реализуемый:
- Для каждого сегмента, у которого свойство Element равно null
- Определить направление поверхности сегмента, смотрящее в комнату
- Определить точку в комнате, которая находится совсем чуть-чуть от середины сегмента границы комнаты.
- Провести воображаемую линию между этой точкой и серединой сегмента границы.
- Проверить, что за элемент находится на середине сегмента. Если там нет элемента, то что-то тут не так.
- Этот элемент и является элементом, который вы ищете, т.е. элемент, образующий сегмент.
Ответ: Спасибо за подсказку. Я уже почти реализовал подобную идею.
С помощью вашего совета, я получил то что нужно.
Теперь нет необходимости использовать соседние сегменты.
Реализация метода GetElementByRay
Рудольф также предоставил свою реализацию алгоритма.
- /// <summary>
- /// Return direction turning 90 degrees
- /// left from given input vector.
- /// </summary>
- public XYZ GetLeftDirection( XYZ direction )
- {
- double x = -direction.Y;
- double y = direction.X;
- double z = direction.Z;
- return new XYZ( x, y, z );
- }
- /// <summary>
- /// Return direction turning 90 degrees
- /// right from given input vector.
- /// </summary>
- public XYZ GetRightDirection( XYZ direction )
- {
- return GetLeftDirection( direction.Negate() );
- }
- /// <summary>
- /// Return the neighbouring BIM element generating
- /// the given room boundary curve c, assuming it
- /// is oriented counter-clockwise around the room
- /// if part of an interior loop, and vice versa.
- /// </summary>
- public Element GetElementByRay(
- UIApplication app,
- Document doc,
- View3D view3d,
- Curve c )
- {
- Element boundaryElement = null;
- // Tolerances
- const double minTolerance = 0.00000001;
- const double maxTolerance = 0.01;
- // Height of ray above room level:
- // ray starts from one foot above room level
- const double elevation = 1;
- // Ray starts not directly from the room border
- // but from a point offset slightly into it.
- const double stepInRoom = 0.1;
- // We could use Line.Direction if Curve c is a
- // Line, but since c also might be an Arc, we
- // calculate direction like this:
- XYZ lineDirection
- = ( c.GetEndPoint( 1 ) - c.GetEndPoint( 0 ) )
- .Normalize();
- XYZ upDir = elevation * XYZ.BasisZ;
- // Assume that the room is on the left side of
- // the room boundary curve and wall on the right.
- // This is valid for both outer and inner room
- // boundaries (outer are counter-clockwise, inner
- // are clockwise). Start point is slightly inside
- // the room, one foot above room level.
- XYZ toRoomVec = stepInRoom * GetLeftDirection(
- lineDirection );
- XYZ pointBottomInRoom = c.Evaluate( 0.5, true )
- + toRoomVec;
- XYZ startPoint = pointBottomInRoom + upDir;
- // We are searching for walls only
- ElementFilter wallFilter
- = new ElementCategoryFilter(
- BuiltInCategory.OST_Walls );
- ReferenceIntersector intersector
- = new ReferenceIntersector( wallFilter,
- FindReferenceTarget.Element, view3d );
- // We don't want to find elements in linked files
- intersector.FindReferencesInRevitLinks = false;
- XYZ toWallDir = GetRightDirection(
- lineDirection );
- ReferenceWithContext context = intersector
- .FindNearest( startPoint, toWallDir );
- Reference closestReference = null;
- if( context != null )
- {
- if( ( context.Proximity > minTolerance )
- && ( context.Proximity < maxTolerance
- + stepInRoom ) )
- {
- closestReference = context.GetReference();
- if( closestReference != null )
- {
- boundaryElement = doc.GetElement(
- closestReference );
- }
- }
- }
- return boundaryElement;
- }
Реализация команды GetBoundarySegmentElement
Я также реализовал небольшую надстройку, которую назвал GetBoundarySegmentElement и добавил туда команду, с помощью которой можно выполнить метод, предоставленный Рудольфом.
- public Result Execute(
- ExternalCommandData commandData,
- ref string message,
- ElementSet elements )
- {
- UIApplication uiapp = commandData.Application;
- UIDocument uidoc = uiapp.ActiveUIDocument;
- Application app = uiapp.Application;
- Document doc = uidoc.Document;
- Selection sel = uidoc.Selection;
- List<Room> rooms = new List<Room>(
- sel.Elements.Cast<Room>() );
- if( 1 != rooms.Count )
- {
- message = "Please select exactly one room.";
- return Result.Failed;
- }
- View3D view3d
- = new FilteredElementCollector( doc )
- .OfClass( typeof( View3D ) )
- .Cast<View3D>()
- .FirstOrDefault<View3D>(
- e => e.Name.Equals( "{3D}" ) );
- if( null == view3d )
- {
- message = "No 3D view named '{3D}' found.";
- return Result.Failed;
- }
- foreach( Room room in rooms )
- {
- IList<IList<BoundarySegment>> loops
- = room.GetBoundarySegments(
- new SpatialElementBoundaryOptions() );
- int n = loops.Count;
- Debug.Print(
- "Room {0} has {1} loop{2}{3}",
- room.Name, n, PluralSuffix( n ),
- DotOrColon( n ) );
- int i = 0;
- foreach( IList<BoundarySegment> loop in loops )
- {
- n = loop.Count;
- Debug.Print(
- " Loop {0} has {1} segment{2}{3}",
- i++, n, PluralSuffix( n ),
- DotOrColon( n ) );
- int j = 0;
- foreach( BoundarySegment seg in loop )
- {
- Element e = seg.Element;
- string s = "Element property";
- if( null == e )
- {
- s = "GetElementByRay";
- e = GetElementByRay( uiapp, doc, view3d,
- seg.Curve );
- }
- Debug.Print(
- " Segment {0}: {1} element {2} returned by {3}",
- j++, CurveString( seg.Curve ),
- ElementDescription( e ), s );
- }
- }
- }
- return Result.Succeeded;
- }
Как всегда, 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
Отклик: когда я читал черновик поста, я заметил следующее:
- foreach( Room room in rooms )
- {
- IList<IList<BoundarySegment>> loops
- = room.GetBoundarySegments(
- new SpatialElementBoundaryOptions() );
Я хочу заметить, что мы должны явно указать в классе SpatialElementBoundaryOptions какое положение нам нужно использовать для определения границы. Нам нужна финишная поверхность.
Вот правильный код:
- SpatialElementBoundaryOptions opt
- = new SpatialElementBoundaryOptions();
- opt.SpatialElementBoundaryLocation
- = SpatialElementBoundaryLocation.Finish;
- IList<IList<BoundarySegment>> loops
- = 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://adn-cis.org/forum/index.php?topic=305
Опубликовано 04.11.2013Отредактировано 04.11.2013 в 12:34:01