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

09/01/2014

Расчет длины наклонного сегмента между двумя трубами

В посте мы обсудим интересную команду, связанную с MEP, которую я реализовал для вычисления наклонного сегмента на хакатоне CASE BIM на AU по предложению Гарри Маттисона (Harry Mattison) и Мэтью Нельсона (Matthew Nelson). 

Команда вычисляет длину сегмента между двумя выбранными трубами и создает видимую линию в модели, для визуализации результата.

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

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

Возможно лучшим способом объяснить задачу – это показать решение в действии перед тем как рассматривать подробную реализацию.

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

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

Небольшое поясняющее видео.




Если же вам больше нравятся картинки, то ниже представлены 6 скриншотов с описанием ситуации.

 

3D вид ДО

 

Вид слева ДО

 

Вид сверху ДО

 

3D вид ПОСЛЕ

 

Вид слева ПОСЛЕ

 

Вид сверху ПОСЛЕ

Если вы посмотрите внимательно, то заметите, что обе трубы стали короче после выполнения команды. При построении линии под 45 градусов, размер труб был соответствующе скорректирован.

Алгоритм поддерживает различный угол наклона, например, 30 или 60 градусов. На данный момент значение угла задано непосредственно в коде.

Реализация команды

Я реализовал код, вычисляющий размер сегмента и создающий линию для визуализации результата в новой команде CmdRollingOffset в примерах The Building Coder.

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

  • Если в модели содержится только две трубы, то они берутся по умолчанию
  • Выбор двух труб перед выполнением команды
  • Запрос выбора труб в процессе выполнения

Код все команды представлен ниже:

Код - C#: [Выделить]
  1. #region Namespaces
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using Autodesk.Revit.ApplicationServices;
  6. using Autodesk.Revit.Attributes;
  7. using Autodesk.Revit.DB;
  8. using Autodesk.Revit.UI;
  9. using Autodesk.Revit.UI.Selection;
  10. using Autodesk.Revit.DB.Plumbing;
  11. using BuildingCoder;
  12. using System.Linq;
  13. #endregion
  14. namespace RollingOffset
  15. {
  16.     [Transaction(TransactionMode.Manual)]
  17.     class Command : IExternalCommand
  18.     {
  19.         const string _prompt
  20.           = "Запустите команду в проекте, "
  21.           + "в котором содержится две параллельные трубы, "
  22.           + "и они будут выбраны автоматически "
  23.           + "В противном случае"
  24.           + "выберите две трубы перед запуском команды "
  25.           + "или выберите их после запуска.";
  26.         /// <summary>
  27.         /// Позволяет выбрать только трубы.
  28.         /// </summary>
  29.         class PipeElementSelectionFilter : ISelectionFilter
  30.         {
  31.             public bool AllowElement(Element e)
  32.             {
  33.                 return e is Pipe;
  34.             }
  35.             public bool AllowReference(Reference r, XYZ p)
  36.             {
  37.                 return true;
  38.             }
  39.         }
  40.         public Result Execute(
  41.           ExternalCommandData commandData,
  42.           ref string message,
  43.           ElementSet elements)
  44.         {
  45.             UIApplication uiapp = commandData.Application;
  46.             UIDocument uidoc = uiapp.ActiveUIDocument;
  47.             Application app = uiapp.Application;
  48.             Document doc = uidoc.Document;
  49.             // Выберем все трубы в модели.
  50.             List<Pipe> pipes = new List<Pipe>(
  51.               new FilteredElementCollector(doc)
  52.                 .OfClass(typeof(Pipe))               
  53.                 .Cast<Pipe>());
  54.             int n = pipes.Count;
  55.             // Если труб меньше чем две
  56.             // то такой проект нам не подходит
  57.             if (2 > n)
  58.             {
  59.                 message = _prompt;
  60.                 return Result.Failed;
  61.             }
  62.             // Если труб всего 2, то их и берем
  63.             if (2 < n)
  64.             {
  65.                 // В противном случае проверяем, не выбраны ли трубы перед запуском команды
  66.                 pipes.Clear();
  67.                 Selection sel = uidoc.Selection;
  68.                 n = sel.Elements.Size;
  69.                 Debug.Print("{0} выбрано элементов.",
  70.                   n);
  71.                 // Если выбрано больше чем две трубы
  72.                 // выбираем две первые
  73.                 if (1 < n)
  74.                 {
  75.                     foreach (Element e in sel.Elements)
  76.                     {
  77.                         Pipe c = e as Pipe;
  78.                         if (null != c)
  79.                         {
  80.                             pipes.Add(c);
  81.                             if (2 == pipes.Count)
  82.                             {
  83.                                 Debug.Print("Нашли две трубы, "
  84.                                   + "Остальное игнорируем.");
  85.                                 break;
  86.                             }
  87.                         }
  88.                     }
  89.                 }
  90.                 // Если трубы не выбраны до сих пор
  91.                 // Даем пользователю выбрать их.
  92.                 if (2 != pipes.Count)
  93.                 {
  94.                     pipes.Clear();
  95.                     try
  96.                     {
  97.                         Reference r = sel.PickObject(
  98.                           ObjectType.Element,
  99.                           new PipeElementSelectionFilter(),
  100.                           "Выберите первую трубу");
  101.                         pipes.Add(doc.GetElement(r.ElementId)
  102.                           as Pipe);
  103.                     }
  104.                     catch (Autodesk.Revit.Exceptions
  105.                       .OperationCanceledException)
  106.                     {
  107.                         return Result.Cancelled;
  108.                     }
  109.                     try
  110.                     {
  111.                         Reference r = sel.PickObject(
  112.                           ObjectType.Element,
  113.                           new PipeElementSelectionFilter(),
  114.                           "Выберите вторую трубу");
  115.                         pipes.Add(doc.GetElement(r.ElementId)
  116.                           as Pipe);
  117.                     }
  118.                     catch (Autodesk.Revit.Exceptions
  119.                       .OperationCanceledException)
  120.                     {
  121.                         return Result.Cancelled;
  122.                     }
  123.                 }
  124.             }
  125.             // Извлекаем необходимые данные из двух труб
  126.             Curve c0 = (pipes[0].Location as LocationCurve).Curve;
  127.             Curve c1 = (pipes[1].Location as LocationCurve).Curve;
  128.             if (!(c0 is Line) || !(c1 is Line))
  129.             {
  130.                 message = _prompt
  131.                   + " Нужны прямые трубы.";
  132.                 return Result.Failed;
  133.             }
  134.             XYZ p00 = c0.GetEndPoint(0);
  135.             XYZ p01 = c0.GetEndPoint(1);
  136.             XYZ p10 = c1.GetEndPoint(0);
  137.             XYZ p11 = c1.GetEndPoint(1);
  138.             XYZ v0 = p01 - p00;
  139.             XYZ v1 = p11 - p10;
  140.             if (!Util.IsParallel(v0, v1))
  141.             {
  142.                 message = _prompt
  143.                   + " Трубы должны быть параллельны.";
  144.                 return Result.Failed;
  145.             }
  146.             // Выберем конечные точки труб, которые 
  147.             // располагаются на бОльшем расстоянии друг от друга
  148.            
  149.             XYZ p0 = p00.DistanceTo(p10) > p01.DistanceTo(p10)
  150.               ? p00
  151.               : p01;
  152.             XYZ p1 = p10.DistanceTo(p0) > p11.DistanceTo(p0)
  153.               ? p10
  154.               : p11;
  155.             XYZ pm = 0.5 * (p0 + p1);
  156.             XYZ v = p1 - p0;
  157.             if (Util.IsParallel(v, v0))
  158.             {
  159.                 message = "Выбранные трубы лежат в одной плоскости";
  160.                 return Result.Failed;
  161.             }
  162.             XYZ z = v.CrossProduct(v1);
  163.             XYZ w = z.CrossProduct(v1).Normalize();
  164.            
  165.             double distanceAcross = Math.Abs(
  166.               v.DotProduct(w));
  167.             // Расстояние между конечными точками           
  168.             double distanceAlong = Math.Abs(
  169.               v.DotProduct(v1.Normalize()));
  170.             Debug.Assert(Util.IsEqual(v.GetLength(),
  171.               Math.Sqrt(distanceAcross * distanceAcross
  172.                 + distanceAlong * distanceAlong)),
  173.               "Ожидаем пифагорово равенство");
  174.             // Угол наклона
  175.             double angle = 45 * Math.PI / 180.0;
  176.             // Противопольжный угол наклона.
  177.             double angle2 = 0.5 * Math.PI - angle;
  178.             double length = distanceAcross * Math.Tan(angle2);
  179.             double halfLength = 0.5 * length;
  180.             // На каком расстоянии друг от друга стали трубы?
  181.             double remainingPipeLength
  182.               = 0.5 * (distanceAlong - length);
  183.             if (0 > v1.DotProduct(v))
  184.             {
  185.                 v1.Negate();
  186.             }
  187.             v1 = v1.Normalize();
  188.             XYZ q0 = p0 + remainingPipeLength * v1;
  189.             XYZ q1 = p1 - remainingPipeLength * v1;
  190.             using (Transaction tx = new Transaction(doc))
  191.             {
  192.                 tx.Start("Создание сегмента");
  193.                 // Укоротим или удлиним существующие трубы
  194.                 (pipes[0].Location as LocationCurve).Curve
  195.                   = Line.CreateBound(p0, q0);
  196.                 (pipes[1].Location as LocationCurve).Curve
  197.                   = Line.CreateBound(p1, q1);
  198.                 // И добавим линиию
  199.                 Creator creator = new Creator(doc);
  200.                 Line line = Line.CreateBound(q0, q1);
  201.                 creator.CreateModelCurve(line);
  202.                 tx.Commit();
  203.             }
  204.             return Result.Succeeded;
  205.         }
  206.     }
  207. }
  208.  

Надеюсь, что информация окажется для вас полезной и интересной.

 

Источник: http://thebuildingcoder.typepad.com/blog/2014/01/calculating-a-rolling-offset-between-two-pipes.html

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

Опубликовано 09.01.2014
Отредактировано 09.01.2014 в 11:28:34