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

16/10/2013

Преобразование геометрии прямоугольника, ограничивающего объект (BoundingBox)

Джон Смит (JonSmith) из компании Construction Industry Solutions Ltd столкнулся еще с одной проблемой, связанной с преобразованием.

Вопрос: Нам нужно преобразовать некоторые элементы в другую систему координат, перед тем как получить границы этого объекта. Мы сейчас делаем это, получив сначала геометрию объекта GeometryObject, вызываем метод GetTransformed для полученной геометрии, и уже на преобразованной геометрии получаем границы с помощью функции GetBoundingBox.

Такой алгоритм работает прекрасно в 2014 версии. Однако в 2013 версии, метод GetBoundingBox на преобразованном GeometryElement возвращает прямоугольник с огромным отрицательным значением максимума и огромным положительным минимумом. Очевидно, что данные ошибочные. Максимум не может быть меньше минимума. Можно ли как-то обойти этот баг в 2013 версии? Я попытался поиграть с различными значениями GeometryOptions, но безрезультатно. Исходная геометрия объекта при все этом выглядит верной.

Ниже представлен кусок кода, демонстрирующий проблему. В реальном проекте я преобразовываю геометрию элемента во что-то значимое, но в этом простом примере я использую идентифицирующую матрицу. По этой причине прямоугольник, ограничивающий объект до преобразования, должен быть точно таким же что и после преобразования. Единственная проблема в этом примере, то что максимальные и минимальные значения граничащего прямоугольника отображаются положительным числом, в то время как на самом деле значение отрицательное. Значения слишком большие для корректного отображения. Пример работает отлично в 2014 версии, но в 2013 значение свойства BoundingBoxXYZ очень странное.

Код - C#: [Выделить]
  1. public void test( Element elem )
  2. {
  3.   Options geomOpts = new Options();
  4.  
  5.   GeometryElement geometryElement
  6.     = elem.get_Geometry( geomOpts );
  7.  
  8.   if( geometryElement == null )
  9.     return;
  10.  
  11.   BoundingBoxXYZ preTransformBox
  12.     = geometryElement.GetBoundingBox();
  13.  
  14.   GeometryElement geometryElementTransformed
  15.     = geometryElement.GetTransformed(
  16.       Transform.Identity );
  17.  
  18.   BoundingBoxXYZ blah = new BoundingBoxXYZ();
  19.  
  20.   BoundingBoxXYZ postTransformBox
  21.     = geometryElementTransformed.GetBoundingBox();
  22.  
  23.   System.Windows.Forms.MessageBox.Show(
  24.     "Pre Min: " + preTransformBox.Min.ToString()
  25.     + "\nPre Max: " + preTransformBox.Min.ToString()
  26.     + "\nPost Min: " + postTransformBox.Min.ToString()
  27.     + "\nPost Max: " + postTransformBox.Min.ToString() );
  28. }

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

Можете пояснить для каких целей вы этим пользуетесь?

Может быть есть другой способ достичь того чего вы хотите?

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

Тем не менее, я рад слышать, что в 2014 версии данная проблема исчезла.

Что касается Revit 2013, то преобразование геометрии должно работать как положено. Мне есть над чем поразмышлять.

В качестве решения я бы посоветовал просто взять исходную геометрию и создать новый BoundingBox с минимальным и максимальным значением точек из геометрии.

Ответ: Отлично – спасибо! Ваш совет мне действительно помог.

Причина, по которой мы использовали геометрию элемента в том, что мы может преобразовать геометрию систему координат сущностей “ECS”, если говорить языком AutoCAD, таким образом мы можем создать ограничивающий прямоугольник. Мы не можем преобразовать сам элемент, так как его геометрия может повлиять на другие элементы, например, присоединенные стены.

Мое решение для Revit 2013 такое. Извлечь все грани всех объемных форм в элементе в исходном элементе. Затем преобразовать все точки в систему координат сущностей, и создать BoundingBox по этим точкам.

Я вытащил этот код в отдельный метод getAlignedBoundingBoxFromElement. Может быть он окажется для кого-то полезным.

Метод содержит код как для Revit 2013 так и для 2014.

Код - C#: [Выделить]
  1.        /// <summary>
  2.         /// Return an aligned bounding box around a given
  3.         /// element. Only works if the element uses a
  4.         /// LocationCurve (not LocationPoint).
  5.         /// </summary>
  6.         /// <param name="elem">Element to return
  7.         /// bounding box for</param>
  8.         /// <returns>Bounding box</returns>
  9.         public static BoundingBoxXYZ
  10.           getAlignedBoundingBoxFromElement(
  11.             Element elem)
  12.         {
  13.             LocationCurve lc = elem.Location
  14.               as LocationCurve;
  15.  
  16.             if (lc != null
  17.               && lc.Curve.IsBound)
  18.             {
  19.                 Options geomOpts = new Options();
  20.  
  21.                 GeometryElement geometryElement
  22.                   = elem.get_Geometry(geomOpts);
  23.  
  24.                 if (geometryElement != null)
  25.                 {
  26.  
  27.                     // transformation matrix from model
  28.                     // to the element curve ECS
  29.                     // to simplify it, we force the transformation
  30.                     // to be aligned to the world XY plane - so
  31.                     // just rotated around the Z axis
  32.  
  33. #if _REVIT2014_
  34.         XYZ StartPoint = lc.Curve.GetEndPoint(0);
  35.         XYZ EndPoint = lc.Curve.GetEndPoint(1);
  36. #else
  37.                     XYZ StartPoint = lc.Curve.get_EndPoint(0);
  38.                     XYZ EndPoint = lc.Curve.get_EndPoint(1);
  39. #endif
  40.  
  41.                     // flatten its Z - where the Z is, is
  42.                     // irrelevant as the bounding box will
  43.                     // determine the height
  44.  
  45.                     EndPoint = new XYZ(EndPoint.X, EndPoint.Y,
  46.                       StartPoint.Z);
  47.  
  48.                     XYZ direction = EndPoint - StartPoint;
  49.                     XYZ normal = XYZ.BasisZ;
  50.  
  51.                     Transform t
  52.                       = Transform.Identity;
  53.  
  54.                     t.Origin = StartPoint;
  55.                     t.BasisX = direction.Normalize();
  56.  
  57.                     t.BasisY = normal.CrossProduct(t.BasisX)
  58.                       .Normalize();
  59.  
  60.                     t.BasisZ = t.BasisX.CrossProduct(t.BasisY)
  61.                       .Normalize();
  62.  
  63.                     // check we have a valid matrix
  64.  
  65.                     if (!t.IsConformal || t.Determinant < 0)
  66.                         return null;
  67.  
  68.                     Transform modelToElementTransform = t.Inverse;
  69.  
  70.                     // transform the geometry into the ECS we
  71.                     // have created to get an aligned bounding box
  72.  
  73.                     GeometryElement geometryElementTransformed
  74.                       = geometryElement.GetTransformed(
  75.                         modelToElementTransform);
  76.  
  77.                     BoundingBoxXYZ ecsBoundingBox
  78.                       = geometryElementTransformed
  79.                         .GetBoundingBox();
  80.  
  81.                     // Revit 2013 Shim
  82.                     // ===============
  83.                     // in Revit 2013, the returned bounding box
  84.                     // is garbage - the Max is hugely negative
  85.                     // and the Min is hugely positive
  86.                     // if this happens then get geometry points
  87.                     // in model coordinates, convert to element
  88.                     // coordinates and create bounding box from
  89.                     // those
  90.                     // NB. we could use this code for 2014 as
  91.                     // well, but the calculation is probably not
  92.                     // as accurate for some situations
  93.  
  94.                     if (ecsBoundingBox.Max.X
  95.                       < ecsBoundingBox.Min.X)
  96.                     {
  97.                         // get points from all edges in all solids
  98.                         // - allows for curves by using tessellated
  99.                         // edges
  100.  
  101.                         List<XYZ> pts = new List<XYZ>();
  102.  
  103.                         foreach (Solid solid in geometryElement
  104.                           .OfType<Solid>())
  105.                         {
  106.                             foreach (Edge edge in solid.Edges)
  107.                                 pts.AddRange(edge.Tessellate());
  108.                         }
  109.  
  110.                         // transform the points into element
  111.                         // coordinates
  112.  
  113.                         ecsBoundingBox = new BoundingBoxXYZ();
  114.  
  115.                         pts = pts.Select(
  116.                             pt => modelToElementTransform.OfPoint(
  117.                               pt))
  118.                           .ToList();
  119.  
  120.                         if (pts.Any())
  121.                         {
  122.                             // calculate the bounding box
  123.  
  124.                             ecsBoundingBox = new BoundingBoxXYZ();
  125.  
  126.                             ecsBoundingBox.Max = new XYZ(
  127.                               pts.Max(pt => pt.X),
  128.                               pts.Max(pt => pt.Y),
  129.                               pts.Max(pt => pt.Z));
  130.  
  131.                             ecsBoundingBox.Min = new XYZ(
  132.                               pts.Min(pt => pt.X),
  133.                               pts.Min(pt => pt.Y),
  134.                               pts.Min(pt => pt.Z));
  135.                         }
  136.                         else
  137.                         {
  138.                             // fail-case - if element has
  139.                             // no solid geometry
  140.  
  141.                             return null;
  142.                         }
  143.                     }
  144.  
  145.                     // finally apply the ECS to Model
  146.                     // transformation back to the bounding box
  147.  
  148.                     ecsBoundingBox.Transform = t.Multiply(
  149.                       ecsBoundingBox.Transform);
  150.  
  151.                     return ecsBoundingBox;
  152.                 }
  153.             }
  154.             return null;
  155.         }

Большое спасибо Джону за исследование и прекрасное решение проблемы.

Источник: http://thebuildingcoder.typepad.com/blog/2013/10/linq-diy-transformed-geometry-bounding-box.html

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

Опубликовано 16.10.2013
Отредактировано 16.10.2013 в 09:50:44