Преобразование геометрии прямоугольника, ограничивающего объект (BoundingBox)
Джон Смит (JonSmith) из компании Construction Industry Solutions Ltd столкнулся еще с одной проблемой, связанной с преобразованием.
Вопрос: Нам нужно преобразовать некоторые элементы в другую систему координат, перед тем как получить границы этого объекта. Мы сейчас делаем это, получив сначала геометрию объекта GeometryObject, вызываем метод GetTransformed для полученной геометрии, и уже на преобразованной геометрии получаем границы с помощью функции GetBoundingBox.
Такой алгоритм работает прекрасно в 2014 версии. Однако в 2013 версии, метод GetBoundingBox на преобразованном GeometryElement возвращает прямоугольник с огромным отрицательным значением максимума и огромным положительным минимумом. Очевидно, что данные ошибочные. Максимум не может быть меньше минимума. Можно ли как-то обойти этот баг в 2013 версии? Я попытался поиграть с различными значениями GeometryOptions, но безрезультатно. Исходная геометрия объекта при все этом выглядит верной.
Ниже представлен кусок кода, демонстрирующий проблему. В реальном проекте я преобразовываю геометрию элемента во что-то значимое, но в этом простом примере я использую идентифицирующую матрицу. По этой причине прямоугольник, ограничивающий объект до преобразования, должен быть точно таким же что и после преобразования. Единственная проблема в этом примере, то что максимальные и минимальные значения граничащего прямоугольника отображаются положительным числом, в то время как на самом деле значение отрицательное. Значения слишком большие для корректного отображения. Пример работает отлично в 2014 версии, но в 2013 значение свойства BoundingBoxXYZ очень странное.
- public void test( Element elem )
- {
- Options geomOpts = new Options();
- GeometryElement geometryElement
- = elem.get_Geometry( geomOpts );
- if( geometryElement == null )
- return;
- BoundingBoxXYZ preTransformBox
- = geometryElement.GetBoundingBox();
- GeometryElement geometryElementTransformed
- = geometryElement.GetTransformed(
- Transform.Identity );
- BoundingBoxXYZ blah = new BoundingBoxXYZ();
- BoundingBoxXYZ postTransformBox
- = geometryElementTransformed.GetBoundingBox();
- System.Windows.Forms.MessageBox.Show(
- "Pre Min: " + preTransformBox.Min.ToString()
- + "\nPre Max: " + preTransformBox.Min.ToString()
- + "\nPost Min: " + postTransformBox.Min.ToString()
- + "\nPost Max: " + postTransformBox.Min.ToString() );
- }
Ответ: Интересный вопрос. Я никогда не пользовался ранее преобразованием геометрии элемента и ограничивающего прямоугольника. Использовал только преобразования самого элемента.
Можете пояснить для каких целей вы этим пользуетесь?
Может быть есть другой способ достичь того чего вы хотите?
Не могли бы вы преобразовать прямоугольник, ограничивающий непосредственно сам элемент, а не его геометрию?
Тем не менее, я рад слышать, что в 2014 версии данная проблема исчезла.
Что касается Revit 2013, то преобразование геометрии должно работать как положено. Мне есть над чем поразмышлять.
В качестве решения я бы посоветовал просто взять исходную геометрию и создать новый BoundingBox с минимальным и максимальным значением точек из геометрии.
Ответ: Отлично – спасибо! Ваш совет мне действительно помог.
Причина, по которой мы использовали геометрию элемента в том, что мы может преобразовать геометрию систему координат сущностей “ECS”, если говорить языком AutoCAD, таким образом мы можем создать ограничивающий прямоугольник. Мы не можем преобразовать сам элемент, так как его геометрия может повлиять на другие элементы, например, присоединенные стены.
Мое решение для Revit 2013 такое. Извлечь все грани всех объемных форм в элементе в исходном элементе. Затем преобразовать все точки в систему координат сущностей, и создать BoundingBox по этим точкам.
Я вытащил этот код в отдельный метод getAlignedBoundingBoxFromElement. Может быть он окажется для кого-то полезным.
Метод содержит код как для Revit 2013 так и для 2014.
- /// <summary>
- /// Return an aligned bounding box around a given
- /// element. Only works if the element uses a
- /// LocationCurve (not LocationPoint).
- /// </summary>
- /// <param name="elem">Element to return
- /// bounding box for</param>
- /// <returns>Bounding box</returns>
- public static BoundingBoxXYZ
- getAlignedBoundingBoxFromElement(
- Element elem)
- {
- LocationCurve lc = elem.Location
- as LocationCurve;
- if (lc != null
- && lc.Curve.IsBound)
- {
- Options geomOpts = new Options();
- GeometryElement geometryElement
- = elem.get_Geometry(geomOpts);
- if (geometryElement != null)
- {
- // transformation matrix from model
- // to the element curve ECS
- // to simplify it, we force the transformation
- // to be aligned to the world XY plane - so
- // just rotated around the Z axis
- #if _REVIT2014_
- XYZ StartPoint = lc.Curve.GetEndPoint(0);
- XYZ EndPoint = lc.Curve.GetEndPoint(1);
- #else
- XYZ StartPoint = lc.Curve.get_EndPoint(0);
- XYZ EndPoint = lc.Curve.get_EndPoint(1);
- #endif
- // flatten its Z - where the Z is, is
- // irrelevant as the bounding box will
- // determine the height
- EndPoint = new XYZ(EndPoint.X, EndPoint.Y,
- StartPoint.Z);
- XYZ direction = EndPoint - StartPoint;
- XYZ normal = XYZ.BasisZ;
- Transform t
- = Transform.Identity;
- t.Origin = StartPoint;
- t.BasisX = direction.Normalize();
- t.BasisY = normal.CrossProduct(t.BasisX)
- .Normalize();
- t.BasisZ = t.BasisX.CrossProduct(t.BasisY)
- .Normalize();
- // check we have a valid matrix
- if (!t.IsConformal || t.Determinant < 0)
- return null;
- Transform modelToElementTransform = t.Inverse;
- // transform the geometry into the ECS we
- // have created to get an aligned bounding box
- GeometryElement geometryElementTransformed
- = geometryElement.GetTransformed(
- modelToElementTransform);
- BoundingBoxXYZ ecsBoundingBox
- = geometryElementTransformed
- .GetBoundingBox();
- // Revit 2013 Shim
- // ===============
- // in Revit 2013, the returned bounding box
- // is garbage - the Max is hugely negative
- // and the Min is hugely positive
- // if this happens then get geometry points
- // in model coordinates, convert to element
- // coordinates and create bounding box from
- // those
- // NB. we could use this code for 2014 as
- // well, but the calculation is probably not
- // as accurate for some situations
- if (ecsBoundingBox.Max.X
- < ecsBoundingBox.Min.X)
- {
- // get points from all edges in all solids
- // - allows for curves by using tessellated
- // edges
- List<XYZ> pts = new List<XYZ>();
- foreach (Solid solid in geometryElement
- .OfType<Solid>())
- {
- foreach (Edge edge in solid.Edges)
- pts.AddRange(edge.Tessellate());
- }
- // transform the points into element
- // coordinates
- ecsBoundingBox = new BoundingBoxXYZ();
- pts = pts.Select(
- pt => modelToElementTransform.OfPoint(
- pt))
- .ToList();
- if (pts.Any())
- {
- // calculate the bounding box
- ecsBoundingBox = new BoundingBoxXYZ();
- ecsBoundingBox.Max = new XYZ(
- pts.Max(pt => pt.X),
- pts.Max(pt => pt.Y),
- pts.Max(pt => pt.Z));
- ecsBoundingBox.Min = new XYZ(
- pts.Min(pt => pt.X),
- pts.Min(pt => pt.Y),
- pts.Min(pt => pt.Z));
- }
- else
- {
- // fail-case - if element has
- // no solid geometry
- return null;
- }
- }
- // finally apply the ECS to Model
- // transformation back to the bounding box
- ecsBoundingBox.Transform = t.Multiply(
- ecsBoundingBox.Transform);
- return ecsBoundingBox;
- }
- }
- return null;
- }
Большое спасибо Джону за исследование и прекрасное решение проблемы.
Источник: 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