Поиск проемов в стене
Вопрос: Я пытаюсь получить с помощью API проемы в стене. Меня в частности интересует координаты прямоугольного проема.
Я попытался использовать метод FindInserts(), но он не возвращает проемы.
Есть ли решение этой проблемы?
Ответ: Интересный вопрос, на который существует несколько ответов.
Можно определить элементы на стене (включая проемы) путем временного удаления этой стены. Такой способ даст вам список всех зависимых объектов, которые удаляются вместе со стеной.
Можно проанализировать геометрию стены до и после удаления вставленных на стену элементов и определить разницу.
Можно также исследовать геометрию стены на виде Фасад и найти проемы таким образом.
Но на мой взгляд лучшим способом является построение луча вдоль стены и определение всех поверхностей, через которые он проходит.
Однако, данный способ работает только если вы знаете высоту, на которой располагается проем, чтобы построить луч именно через него.
Подобный способ демонстрируется в проектах FindReferencesByDirection и FindColumns в примерах, поставляемых вместе с Revit SDK.
Попробуйте, расскажите что в итоге получится.
Ответ: Отлично. Метод с лучом работает превосходно. Я использовал 2D вид, но перед этим, на виде «Фасад» я нашел высоту.
Для всех, кто интересуется данным методом для обнаружения проемов в стене, обратите внимание, что базовую точку лучше установить на небольшом расстоянии от стены, чтобы можно было определить, не начинается ли стена с проема, как на этом примере:
Псевдо-код моего решения:
- WallOpening2D – простой класс с двумя координатами и другой базовой информацией.
- List<WallOpening2D> GetWallOpenings (
- Wall wall,
- ViewPlan view )
- {
- var rayStart = new XYZ(
- wallOrigin.X - wallDirection.X,
- wallOrigin.Y - wallDirection.Y,
- GetElevation(view));
- pointList = (from reference in
- new ReferenceIntersector(view)
- .Find(rayStart, wallDirection)
- .where(IsSurface)
- .where(ref => ref.Proximity
- < wallLength + "step outside"))
- select reference.GetReference.GlobalPoint)
- if(!pointList.First().IsAlmostEqualTo(wallOrigin) //Проверяем, что первая точка не является границей стены
- pointList.Insert(0, wallOrigin);
- else
- pointList.remove(pointList(First));
- if(!IsEven(pointList.Count) //если не четное количество, то стена заканчивается проемом
- pointList.Add(wallEndPoint);
- var wallOpenings = new List();
- for(i = 0; i < pointList.Count -1; i += 2)
- wallOpenings.Add(new WallOpening2D(pointList[i], pointList[i+1]);
- return wallOpenings;
- }
Ответ: Рад что мой совет помог. И спасибо за псевдо-код. Однако я попробвал его реализовать и столкнулся с одной проблемой:
error CS1502: The best overloaded method match for 'Autodesk.Revit.DB.ReferenceIntersector.ReferenceIntersector(Autodesk.Revit.DB.View3D)' has some invalid arguments
error CS1503: Argument 1: cannot convert from 'Autodesk.Revit.DB.ViewPlan' to 'Autodesk.Revit.DB.View3D'
На текущий момент моя реализация такова:
- List GetWallOpenings(
- Wall wall,
- ViewPlan view )
- {
- Document doc = wall.Document;
- Level level = doc.GetElement( view.LevelId ) as Level;
- double elevation = level.Elevation;
- Curve c = (wall.Location as LocationCurve).Curve;
- XYZ wallOrigin = c.GetEndPoint(0);
- XYZ wallEndPoint = c.GetEndPoint(1);
- XYZ wallDirection =wallEndPoint - wallOrigin;
- double wallLength = wallDirection.GetLength();
- wallDirection = wallDirection.Normalize();
- UV offset = new UV( wallDirection.X, wallDirection.Y );
- double step_outside = offset.GetLength();
- XYZ rayStart = new XYZ( wallOrigin.X - offset.U,
- wallOrigin.Y - offset.V, elevation );
- ReferenceIntersector intersector = new ReferenceIntersector( view ); // *** тут ошибка ***
- IList refs = intersector.Find( rayStart, wallDirection );
- List pointList = new List( refs
- .Where( r => IsSurface(r.GetReference()))
- .Where( r => r.Proximity < wallLength + step_outside)
- .Select( r => r.GetReference().GlobalPoint) );
- // Проверяем, если перввая точка не является границей стены
- // Если так, то стена начнается с проема
- if(!pointList.First().IsAlmostEqualTo(wallOrigin))
- {
- pointList.Insert(0, wallOrigin);
- }
- else
- {
- pointList.Remove(pointList.First());
- }
- // Если количество товек нечетное, то стена
- // заканчивается проемом
- if(!IsEven(pointList.Count))
- {
- pointList.Add(wallEndPoint);
- }
- int n = pointList.Count;
- var wallOpenings = new List( n / 2 );
- for( int i = 0; i < n; i += 2 )
- {
- wallOpenings.Add( new WallOpening2d {
- Start = pointList[i],
- End = pointList[i + 1] } );
- }
- return wallOpenings;
- }
Очевидно, нужно передать 3D вид, а не план.
Также я получил довольно странный набор точек на простой стене с четырьмя проемами, ни один из которых не на границе стены. Все в середине.
Начальная точка появляется дважды. А конечная граница стены не появляется вовсе. Точки не отсортированы по порядку появления:
Нужно конечно подчстить код и добавить защиту от дурака для более надежного результата.
Рабочую команду можно найти в примерах The Building Coder. Команда называется CmdWallOpenings.
Ответ: Немного корректировки в мой псевдо-код:
- Вы правы насчет 3D вида. Я использую 2D вид чтобы получить высоту. Для ReferenceIntersector я использую 3D вид по умолчанию. Нужнно только удостовериться, что он не имеет области подрезки.
- var default3DView
- = new FilteredElementCollector(doc)
- .OfClass(typeof (View3D))
- .ToElements()
- .Cast()
- .FirstOrDefault( v
- => v != null
- && !v.IsTemplate
- && v.Name.Equals("{3D}"));
Конечно, можно и создать 3D вид, если нужно.
- Я забыл уточнить, что ReferenceIntersector должен возвратить только поверхности, принадлежащие данной стене
- var referenceIntersector
- = new ReferenceIntersector( wall.Id,
- FindReferenceTarget.Face, default3DView);
- Начальная точка появляется дважды. А конечная граница стены не появляется вовсе. Точки не отсортированы по порядку появления
Точки будут отсортированы, если доабвить идентификатор стены в конструктор ReferenceIntersector. В вашем случае точки отсортированы по элементам, с которыми идет пересечение.
Чтобы получить конечную точку, важно поставить смещение снаружи стены, как в этом выражении:
- .Where( r => r.Proximity < wallLength + step_outside)
Начальная точка появляется дважды, возможно из-за этого условия - !pointList.First().IsAlmostEqualTo(wallOrigin) в коде
- if(!pointList.First().IsAlmostEqualTo(wallOrigin))
- {
- pointList.Insert(0, wallOrigin);
- }
Это происходит потому, что координата Z базовой точки стены не обязательно будт совпадать с кординатой Z луча.
Измененный код:
- Curve c = (wall.Location as LocationCurve).Curve;
- var wallStartPoint = new XYZ( c.GetEndPoint(0).X,
- c.GetEndPoint(0).Y, elevation);
- var rayStart = new XYZ(
- wallStartPoint.X - wallLine.Direction.X,
- wallStartPoint.Y - wallLine.Direction.Y,
- wallStartPoint.Z);
- ...
- if (!pointList.First().IsAlmostEqualTo(wallStartPoint))
- pointList.Insert(0, wallStartPoint);
Мой код теперь работает достаточно неплохо. Спасибо за предложение использовать луч J
Ответ: Большое спасибо, что ответили на мои вопросы.
Если вы заметили, то я тоже нашел и поправил все эти проблемы в репозитории, хотя и немного по своему.
1) Я просто использовал текущий 3D вид.
2) Ага, тоже заметил и добавил это
3) Нет. Точки, которые я перечислил выше являются точным результатом того, что найдено, без удалений или добавлений. Я думаю проблема в моем случае состоит в том, что я использовал высоту равной 0, таким образом луч проходил прямо по нижней границе стены. В этом случае результат оказался неточным. Я в итоге немного поднял луч и получил более корректные и красивые результаты.
4) Мой код тоже теперь работает хорошо. Я обновил его на GitHub. Если у вас будут еще обновления, дайте мне знать.
Дополнение 1: Дополнение по поводу 3D вида по умолчанию. При совместной работе нет 3D вида по умолчанию. И если пользователь созадет новый 3D вид по умолчанию, то создается вид c именем {3d – имя пользователя}. Все маленькими буквами.
Дополнение 2: Вместо использования одного луча, может быть использовать несколько, типа как расческа.
Ответ: Полностью согласен. Нужно задать параметр – минимальный размер проема, затем пройтись «расческой» по всей стене. Получить все точки, отсортировать их, исключить дубликаты и готово.
Реализация команды CmdWallOpenings
Как я сказал, я реализовал и протестировал алгоритм в новой команде CmdWallOpenings. Я почистил код, сделал поправки, как было описано выше. Они в релизе 2016.0.125.1.
Также я провел тестирование на 3 различных стенах.
Каждая из них имеет 4 проема. Запустив команду CmdWallOpenings для каждой из стен, мы получим вот такой результат:
- 4 openings found:
- ((-0.42,18.27,0.1)-(2.59,18.27,0.1))
- ((5.49,18.27,0.1)-(8.49,18.27,0.1))
- ((9.43,18.27,0.1)-(12.43,18.27,0.1))
- ((13.36,18.27,0.1)-(16.37,18.27,0.1))
- 4 openings found:
- ((0.78,4.77,0)-(2.73,5.68,0.1))
- ((4.47,6.5,0.1)-(7.19,7.76,0.1))
- ((8.93,8.58,0.1)-(11.66,9.84,0.1))
- ((13.1,10.52,0.1)-(15.82,11.79,0.1))
- 4 openings found:
- ((2.23,-3.24,0.1)-(5.12,-2.41,0.1))
- ((6.96,-1.88,0.1)-(9.85,-1.06,0.1))
- ((12.32,-0.35,0.1)-(15.21,0.48,0.1))
- ((15.79,0.65,0.1)-(17.24,1.06,0))
В текущей реализации данные о проеме включают в себя начальную и конечную точки:
- /// <summary>
- /// Простой класс с двумя координатами
- /// и другой базовой информации
- /// </summary>
- class WallOpening2d
- {
- //public ElementId Id { get; set; }
- public XYZ Start { get; set; }
- public XYZ End { get; set; }
- override public string ToString()
- {
- return "("
- //+ Id.ToString() + "@"
- + Util.PointString( Start ) + "-"
- + Util.PointString( End ) + ")";
- }
- } [$/code]]
- Я применил смещение для поднятия луча отночительно уровня пола, тем самым избежав пересечения с нижней гранью стены.
- Это же смещение было применено для удлинения луча за границы стены.
- Код - C#: [Выделить]
- /// <summary>
- /// Немного поднимемся от стены
- /// </summary>
- const double _offset = 0.1; // футы
Следующие два метода осуществляют проверку того, является ли число четным и является ли ссылка поверхностью.
- /// <summary>
- /// Является ли число четным
- /// </summary>
- static bool IsEven( int i )
- {
- return 0 == i % 2;
- }
- /// <summary>
- /// Является ли ссылка поверхностью
- /// </summary>
- static bool IsSurface( Reference r )
- {
- return ElementReferenceType.REFERENCE_TYPE_SURFACE
- == r.ElementReferenceType;
- }
Последение в принципе можно и опустить, так как мы уже определили, что нас интересует пересечение только с поверхностями.
Метод сравнения для поиска дубликатов:
- class XyzEqualityComparer : IEqualityComparer<XYZ>
- {
- public bool Equals( XYZ a, XYZ b )
- {
- return Util.IsEqual( a, b );
- }
- public int GetHashCode( XYZ a )
- {
- return Util.PointString( a ).GetHashCode();
- }
- }
С этими вспомогательным методами, главный метод GetWallOpenings выглядит вот так:
- /// <summary>
- /// Извлекаем все проемы в стене
- /// включая те, что находятся в начале и в конце стены
- /// </summary>
- List<WallOpening2d> GetWallOpenings(
- Wall wall,
- View3D view )
- {
- Document doc = wall.Document;
- Level level = doc.GetElement( wall.LevelId ) as Level;
- double elevation = level.Elevation;
- Curve c = ( wall.Location as LocationCurve ).Curve;
- XYZ wallOrigin = c.GetEndPoint( 0 );
- XYZ wallEndPoint = c.GetEndPoint( 1 );
- XYZ wallDirection = wallEndPoint - wallOrigin;
- double wallLength = wallDirection.GetLength();
- wallDirection = wallDirection.Normalize();
- UV offsetOut = _offset * new UV( wallDirection.X, wallDirection.Y );
- XYZ rayStart = new XYZ( wallOrigin.X - offsetOut.U,
- wallOrigin.Y - offsetOut.V, elevation + _offset );
- ReferenceIntersector intersector
- = new ReferenceIntersector( wall.Id,
- FindReferenceTarget.Face, view );
- IList<ReferenceWithContext> refs
- = intersector.Find( rayStart, wallDirection );
- // Извлекаем точки пересечения:
- // - только поверхности
- // - по все длине стены плюс смещение
- // - сортируем по мере появления
- // - исключая дубликаты.
- List<XYZ> pointList = new List<XYZ>( refs
- .Where<ReferenceWithContext>( r => IsSurface(
- r.GetReference() ) )
- .Where<ReferenceWithContext>( r => r.Proximity
- < wallLength + _offset + _offset )
- .OrderBy<ReferenceWithContext, double>(
- r => r.Proximity )
- .Select<ReferenceWithContext, XYZ>( r
- => r.GetReference().GlobalPoint )
- .Distinct<XYZ>( new XyzEqualityComparer() ) );
- // Проверяем если первая точка на границе стены
- // Если так, что стена не начинается с проема
- // и ее можно удалить
- XYZ q = wallOrigin + _offset * XYZ.BasisZ;
- bool wallHasFaceAtStart = Util.IsEqual(
- pointList[0], q );
- if( wallHasFaceAtStart )
- {
- pointList.RemoveAll( p
- => Util.IsEqual( p, q ) );
- }
- else
- {
- pointList.Insert( 0, wallOrigin );
- }
- // Проверяем если последняя точка на границе стены
- // Если так, что стена не заканчивается проемом
- // и ее можно удалить
- q = wallEndPoint + _offset * XYZ.BasisZ;
- bool wallHasFaceAtEnd = Util.IsEqual(
- pointList.Last(), q );
- if( wallHasFaceAtEnd )
- {
- pointList.RemoveAll( p
- => Util.IsEqual( p, q ) );
- }
- else
- {
- pointList.Add( wallEndPoint );
- }
- int n = pointList.Count;
- Debug.Assert( IsEven( n ),
- "expected an even number of opening sides" );
- var wallOpenings = new List<WallOpening2d>(
- n / 2 );
- for( int i = 0; i < n; i += 2 )
- {
- wallOpenings.Add( new WallOpening2d
- {
- Start = pointList[i],
- End = pointList[i + 1]
- } );
- }
- return wallOpenings;
- }
Во внешней команде в методе Execute мы делаем следующее:
- Проверяем, что мы находимся в валидном контексте
- Выбираем стену или используем ту, которую выделили перед выполнением команды
- Извлекаем все проемы
- Выводим результат
- public Result Execute(
- ExternalCommandData commandData,
- ref string message,
- ElementSet elements )
- {
- UIApplication uiapp = commandData.Application;
- UIDocument uidoc = uiapp.ActiveUIDocument;
- Document doc = uidoc.Document;
- if( null == doc )
- {
- message = "Please run this command in a valid document.";
- return Result.Failed;
- }
- View3D view = doc.ActiveView as View3D;
- if( null == view )
- {
- message = "Please run this command in a 3D view.";
- return Result.Failed;
- }
- Element e = Util.SelectSingleElementOfType(
- uidoc, typeof( Wall ), "wall", true );
- List<WallOpening2d> openings = GetWallOpenings(
- e as Wall, view );
- int n = openings.Count;
- string msg = string.Format(
- "{0} opening{1} found{2}",
- n, Util.PluralSuffix( n ),
- Util.DotOrColon( n ) );
- Util.InfoMsg2( msg, string.Join(
- "\r\n", openings ) );
- return Result.Succeeded;
- }
Источник: http://thebuildingcoder.typepad.com/blog/2015/12/retrieving-wall-openings-and-sorting-points.html
Обсуждение: http://adn-cis.org/forum/index.php?topic=
Опубликовано 27.07.2016Отредактировано 28.07.2016 в 07:53:37