Расчет длины наклонного сегмента между двумя трубами
В посте мы обсудим интересную команду, связанную с MEP, которую я реализовал для вычисления наклонного сегмента на хакатоне CASE BIM на AU по предложению Гарри Маттисона (Harry Mattison) и Мэтью Нельсона (Matthew Nelson).
Команда вычисляет длину сегмента между двумя выбранными трубами и создает видимую линию в модели, для визуализации результата.
Чтобы лучше понять, что же делает команда, представьте себе две параллельные трубы, смещенные друг от друга во всех трех направлениях.
Задача состоит в том, что нужно вычислить длину наклонного сегмента, для соединения двух труб, таким образом, чтобы угол соединения соответствовал заданному.
Возможно лучшим способом объяснить задачу – это показать решение в действии перед тем как рассматривать подробную реализацию.
Возьмем две параллельные, смещенные друг от друга, трубы. Нужно решить, на каком расстоянии друг от друга их нужно разместить, чтобы в итоге соединить их сегментом под заданным углом.
Я решил взять концы труб, находящиеся ближе друг к другу и поместить сегмент таким образом, чтобы его центр совпадал с центром заданных труб.
Небольшое поясняющее видео.
Если же вам больше нравятся картинки, то ниже представлены 6 скриншотов с описанием ситуации.
3D вид ДО
Вид слева ДО
Вид сверху ДО
3D вид ПОСЛЕ
Вид слева ПОСЛЕ
Вид сверху ПОСЛЕ
Если вы посмотрите внимательно, то заметите, что обе трубы стали короче после выполнения команды. При построении линии под 45 градусов, размер труб был соответствующе скорректирован.
Алгоритм поддерживает различный угол наклона, например, 30 или 60 градусов. На данный момент значение угла задано непосредственно в коде.
Реализация команды
Я реализовал код, вычисляющий размер сегмента и создающий линию для визуализации результата в новой команде CmdRollingOffset в примерах The Building Coder.
Для максимальной гибкости и удобства тестирования, команда поддерживает 3 способа выбора труб, между которыми необходимо создать сегмент:
- Если в модели содержится только две трубы, то они берутся по умолчанию
- Выбор двух труб перед выполнением команды
- Запрос выбора труб в процессе выполнения
Код все команды представлен ниже:
- #region Namespaces
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using Autodesk.Revit.ApplicationServices;
- using Autodesk.Revit.Attributes;
- using Autodesk.Revit.DB;
- using Autodesk.Revit.UI;
- using Autodesk.Revit.UI.Selection;
- using Autodesk.Revit.DB.Plumbing;
- using BuildingCoder;
- using System.Linq;
- #endregion
- namespace RollingOffset
- {
- [Transaction(TransactionMode.Manual)]
- class Command : IExternalCommand
- {
- const string _prompt
- = "Запустите команду в проекте, "
- + "в котором содержится две параллельные трубы, "
- + "и они будут выбраны автоматически "
- + "В противном случае"
- + "выберите две трубы перед запуском команды "
- + "или выберите их после запуска.";
- /// <summary>
- /// Позволяет выбрать только трубы.
- /// </summary>
- class PipeElementSelectionFilter : ISelectionFilter
- {
- public bool AllowElement(Element e)
- {
- return e is Pipe;
- }
- public bool AllowReference(Reference r, XYZ p)
- {
- return true;
- }
- }
- public Result Execute(
- ExternalCommandData commandData,
- ref string message,
- ElementSet elements)
- {
- UIApplication uiapp = commandData.Application;
- UIDocument uidoc = uiapp.ActiveUIDocument;
- Application app = uiapp.Application;
- Document doc = uidoc.Document;
- // Выберем все трубы в модели.
- List<Pipe> pipes = new List<Pipe>(
- new FilteredElementCollector(doc)
- .OfClass(typeof(Pipe))
- .Cast<Pipe>());
- int n = pipes.Count;
- // Если труб меньше чем две
- // то такой проект нам не подходит
- if (2 > n)
- {
- message = _prompt;
- return Result.Failed;
- }
- // Если труб всего 2, то их и берем
- if (2 < n)
- {
- // В противном случае проверяем, не выбраны ли трубы перед запуском команды
- pipes.Clear();
- Selection sel = uidoc.Selection;
- n = sel.Elements.Size;
- Debug.Print("{0} выбрано элементов.",
- n);
- // Если выбрано больше чем две трубы
- // выбираем две первые
- if (1 < n)
- {
- foreach (Element e in sel.Elements)
- {
- Pipe c = e as Pipe;
- if (null != c)
- {
- pipes.Add(c);
- if (2 == pipes.Count)
- {
- Debug.Print("Нашли две трубы, "
- + "Остальное игнорируем.");
- break;
- }
- }
- }
- }
- // Если трубы не выбраны до сих пор
- // Даем пользователю выбрать их.
- if (2 != pipes.Count)
- {
- pipes.Clear();
- try
- {
- Reference r = sel.PickObject(
- ObjectType.Element,
- new PipeElementSelectionFilter(),
- "Выберите первую трубу");
- pipes.Add(doc.GetElement(r.ElementId)
- as Pipe);
- }
- catch (Autodesk.Revit.Exceptions
- .OperationCanceledException)
- {
- return Result.Cancelled;
- }
- try
- {
- Reference r = sel.PickObject(
- ObjectType.Element,
- new PipeElementSelectionFilter(),
- "Выберите вторую трубу");
- pipes.Add(doc.GetElement(r.ElementId)
- as Pipe);
- }
- catch (Autodesk.Revit.Exceptions
- .OperationCanceledException)
- {
- return Result.Cancelled;
- }
- }
- }
- // Извлекаем необходимые данные из двух труб
- Curve c0 = (pipes[0].Location as LocationCurve).Curve;
- Curve c1 = (pipes[1].Location as LocationCurve).Curve;
- if (!(c0 is Line) || !(c1 is Line))
- {
- message = _prompt
- + " Нужны прямые трубы.";
- return Result.Failed;
- }
- XYZ p00 = c0.GetEndPoint(0);
- XYZ p01 = c0.GetEndPoint(1);
- XYZ p10 = c1.GetEndPoint(0);
- XYZ p11 = c1.GetEndPoint(1);
- XYZ v0 = p01 - p00;
- XYZ v1 = p11 - p10;
- if (!Util.IsParallel(v0, v1))
- {
- message = _prompt
- + " Трубы должны быть параллельны.";
- return Result.Failed;
- }
- // Выберем конечные точки труб, которые
- // располагаются на бОльшем расстоянии друг от друга
- XYZ p0 = p00.DistanceTo(p10) > p01.DistanceTo(p10)
- ? p00
- : p01;
- XYZ p1 = p10.DistanceTo(p0) > p11.DistanceTo(p0)
- ? p10
- : p11;
- XYZ pm = 0.5 * (p0 + p1);
- XYZ v = p1 - p0;
- if (Util.IsParallel(v, v0))
- {
- message = "Выбранные трубы лежат в одной плоскости";
- return Result.Failed;
- }
- XYZ z = v.CrossProduct(v1);
- XYZ w = z.CrossProduct(v1).Normalize();
- double distanceAcross = Math.Abs(
- v.DotProduct(w));
- // Расстояние между конечными точками
- double distanceAlong = Math.Abs(
- v.DotProduct(v1.Normalize()));
- Debug.Assert(Util.IsEqual(v.GetLength(),
- Math.Sqrt(distanceAcross * distanceAcross
- + distanceAlong * distanceAlong)),
- "Ожидаем пифагорово равенство");
- // Угол наклона
- double angle = 45 * Math.PI / 180.0;
- // Противопольжный угол наклона.
- double angle2 = 0.5 * Math.PI - angle;
- double length = distanceAcross * Math.Tan(angle2);
- double halfLength = 0.5 * length;
- // На каком расстоянии друг от друга стали трубы?
- double remainingPipeLength
- = 0.5 * (distanceAlong - length);
- if (0 > v1.DotProduct(v))
- {
- v1.Negate();
- }
- v1 = v1.Normalize();
- XYZ q0 = p0 + remainingPipeLength * v1;
- XYZ q1 = p1 - remainingPipeLength * v1;
- using (Transaction tx = new Transaction(doc))
- {
- tx.Start("Создание сегмента");
- // Укоротим или удлиним существующие трубы
- (pipes[0].Location as LocationCurve).Curve
- = Line.CreateBound(p0, q0);
- (pipes[1].Location as LocationCurve).Curve
- = Line.CreateBound(p1, q1);
- // И добавим линиию
- Creator creator = new Creator(doc);
- Line line = Line.CreateBound(q0, q1);
- creator.CreateModelCurve(line);
- tx.Commit();
- }
- return Result.Succeeded;
- }
- }
- }
Надеюсь, что информация окажется для вас полезной и интересной.
Источник: 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