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

08/03/2015

Определение типа поверхности под выбранной точкой

Вопрос: Я пишу надстройку для Revit 2015 где мне нужно вставить семейство в проект.

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

Вот мой код для цилиндрической поверхности:

Код - C#: [Выделить]
  1.   Reference r = uidoc.Selection.PickObject(
  2.     ObjectType.Face, "Выберите точку на поверхности "
  3.     + "для вставки семейства." );
  4.  
  5.   Element e = doc.GetElement( r.ElementId );
  6.  
  7.   GeometryObject obj
  8.     = e.GetGeometryObjectFromReference( r );
  9.  
  10.   //PlanarFace face = obj as PlanarFace;
  11.   CylindricalFace face = obj as CylindricalFace;
  12.  
  13.   XYZ p = r.GlobalPoint;
  14.   XYZ v = face.Axis.CrossProduct( XYZ.BasisZ );
  15.   if( v.IsZeroLength() )
  16.   {
  17.     v = face.Axis.CrossProduct( XYZ.BasisX );
  18.   }
  19.   doc.Create.NewFamilyInstance( r, p, v, symbol );

И для плоской поверхности:

Код - C#: [Выделить]
  1.   Reference r = uidoc.Selection.PickObject(
  2.     ObjectType.Face, " Выберите точку на поверхности "
  3.     + "для вставки семейства.");
  4.  
  5.   Element e = doc.GetElement( r.ElementId );
  6.  
  7.   GeometryObject obj
  8.     = e.GetGeometryObjectFromReference( r );
  9.  
  10.   PlanarFace face = obj as PlanarFace;
  11.   //CylindricalFace face = obj as CylindricalFace;
  12.  
  13.   XYZ p = r.GlobalPoint;
  14.   XYZ v = face.Normal.CrossProduct( XYZ.BasisZ );
  15.   if( v.IsZeroLength() )
  16.   {
  17.     v = face.Normal.CrossProduct( XYZ.BasisX );
  18.   }
  19.   doc.Create.NewFamilyInstance( r, p, v, symbol );

Почти ничего не меняется, за исключением определения вектора v.

У меня есть еще и другие вопросы:

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

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

Ответ: Спасибо за вопросы.

Идея проекта выглядит неплохо.

Проверка типа поверхности

Вы можете проеверить тип поверхности довольно легко. Например, вот так:

Код - C#: [Выделить]
  1.   if( obj is PlanarFace )
  2.   {
  3.     PlanarFace planarFace = obj as PlanarFace;
  4.  
  5.     // обрабатываем плоскую поверхность
  6.   }
  7.   else if( obj is CylindricalFace )
  8.   {
  9.     CylindricalFace cylindricalFace = obj
  10.       as CylindricalFace;
  11.  
  12.     // и цилиндрическую.
  13.   }

Хотя было бы лучше не разделять цилиндрическую и плоскую грани поверхности. Все что вам нужно – это вектор v, верно? И вы хотите, чтобы он относился к выбранной поверхности?

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

Определение общей касательной поверхности

 Заданная в Revit API геометрическая рань – это наиболее легкий способ определить касательный вектор на заданной точке. Это можно сделать с помощью метода Face.ComputeDerivatives.

Единственное что требуется для использования данного метода, это координаты UV, лежащие на поверхности грани в координатной системе этой грани. Точка XYZ в трехмерном пространстве определяется с помощью метода PickObject. Можно воспользоваться методом Face.Project, чтобы определить нужную точку на грани.

Таким образом, ComputeDerivatives можно использовать для определения касательной поверхности вне зависимости от ее типа:

Код - C#: [Выделить]
  1.   /// <summary>
  2.   /// Помещает экземпляр семейства на выбранную поверхность
  3.   /// существующего элемента
  4.   /// </summary>
  5.   FamilyInstance PlaceFamilyInstanceOnFace(
  6.     UIDocument uidoc,
  7.     FamilySymbol symbol )
  8.   {
  9.     Document doc = uidoc.Document;
  10.  
  11.     Reference r = uidoc.Selection.PickObject(
  12.       ObjectType.Face, " Выберите точку на поверхности "
  13.     + "для вставки семейства.");
  14.  
  15.     Element e = doc.GetElement( r.ElementId );
  16.  
  17.     GeometryObject obj
  18.       = e.GetGeometryObjectFromReference( r );
  19.  
  20.     XYZ p = r.GlobalPoint;
  21.  
  22.     // Не зацикливаясь на конкретных типах поверхностей,
  23.     // будем использовать общий метод
  24.  
  25.     Face face = obj as Face;
  26.     IntersectionResult ir = face.Project( p );
  27.     UV q = ir.UVPoint;
  28.     Transform t = face.ComputeDerivatives( q );
  29.     XYZ v = t.BasisX; // or BasisY, or whatever...
  30.  
  31.     return doc.Create.NewFamilyInstance(r, p, v, symbol);
  32.   }

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

Непосредственное использование UV точки

Рассмотрим еще один вариант. Я уже отмечал, что метод объект Referencе, возвращаемый методом PickPoint, предоставляет доступ к точке UV на поверхности, поэтому уже не нужно делать проекцию 3D точки на поверхность. Код становится гораздо проще:

Код - C#: [Выделить]
  1.   Reference r = uidoc.Selection.PickObject(
  2.     ObjectType.Face, "Выберите точку на поверхности "
  3.     + " для вставки семейства" );
  4.  
  5.   Element e = doc.GetElement( r.ElementId );
  6.  
  7.   GeometryObject obj
  8.     = e.GetGeometryObjectFromReference( r );
  9.  
  10.   Face face = obj as Face;
  11.   UV q = r.UVPoint;
  12.  
  13.   Transform t = face.ComputeDerivatives( q );
  14.   XYZ v = t.BasisX; // or BasisY, or whatever...
  15.  
  16.   return doc.Create.NewFamilyInstance( r, p, v, symbol );

Путь к файлу семейства

Для того чтобы пользователь могу выбрать путь и название семейство для вставки, можно использовать стандартным классом .NET OpenFileDialog для выбора файла семейства rfa. После того, как файл выбран, можно его загружать с помощью метода LoadFamily.

Проверка на пересечения и коллизии

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

  • Определить твердотельный объект вставленного семейства (solid)
  • Создать фильтр ElementIntersectsSolidFilter, используя этот объект.
  • Получить все элементы, извлеченные с помощью фильтра.

В примерах The Building Coder есть команда CmdCollectorPerformance, в котором вызывается метод GetInstancesIntersectingElement для демонстрации использования фильтра ElementIntersectsSolidFilter.

Также на сайте The Building Coder есть множество тем, связанных с определением пересечений и поиска коллизий (на англ.)

Источник: http://thebuildingcoder.typepad.com/blog/2015/02/determining-the-face-tangent-at-a-picked-point.html

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

Опубликовано 08.03.2015
Отредактировано 08.03.2015 в 16:47:47