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

12/11/2013

Как разместить равноудаленные друг от друга точки вдоль кривой

Как можно сгенерировать список точек, равноудаленных друг от друга, вдоль кривой?

На самом деле это довольно нетривиальная задача, так как Revit API содержит лишь методы для работы с кривыми основываясь на внутренней параметризации кривых, и не содержит методов для работы с кривыми в системе координат проекта.

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

Сейчас я расскажу вам, как решить задачу размещения равноудаленных точек вдоль кривой, используя расстояние, измеренное в системе координат проекта, вместо внутренних параметризованных, предложенное одним из разработчиков:

Вопрос: Я пытаюсь вычислить координаты точек, таким образом, чтоб они были равноудалены друг от друга на спалйне.В Revit API есть метод Curve.Evaluate. В качестве параметра ему необходимо передать параметр кривой. Я пытался воспользоваться следующим кодом, чтобы найти параметры кривой, чтобы вычислить координаты точек на требуем расстоянии:

Код - C#: [Выделить]
  1.   double param1 = curve.GetEndParameter(0);
  2.   double param2 = curve.GetEndParameter(1);
  3.  
  4.   double paramCalc = param1 + ((param2 - param1)
  5.     * requiredDist / curveLength);
  6.  
  7.   XYZ evaluatedPoint = null;
  8.  
  9.   if (curve.IsInside(paramCalc))
  10.   {
  11.     double normParam = curve
  12.       .ComputeNormalizedParameter(paramCalc);
  13.  
  14.     evaluatedPoint = curve.Evaluate(
  15.       normParam, true)));
  16.   }

Этот код прекрасно работает с дугами и прямыми линиями, но не со сплайнами.

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

Есть ли какой-нибудь другой способ, чтобы вычислить точки на определенном расстоянии друг от друга на сплайне?

Я создал пример внешней команды, где я выделяю кружками одинаковое расстояние между точками вдоль кривой. Результат выглядит вот так:

 

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

Как можно это исправить?

Ответ: Я рад сообщить что это довольно легко реализовать.

Как я уже говорил, недавно я обсуждал генерацию кривой между двумя другими кривыми.

В том примере я двигался по кривой фиксированным шагом в параметризированном пространстве кривой.

Равномерное расстояние в параметризованном пространстве превращается в неравномерное в системе координат проекта, особенно когда сплайн содержит резкие изгибы, как в вашем примере.

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

Если количество промежуточных точек, которое можно получить с помощью стандартного в Revit API метода Tesselate будет достаточным, то вы можете использовать это значение в качестве шага для передвижения по кривой. Если же вам нужна более высокая точность, то вы можете генерировать промежуточные точки самостоятельно с помощью метода Evaluate или ComputeDerivatives.

Я реализовал новую внешнюю команду и назвал ее EquiDistantPointEval для демонстрации предложенного алгоритма.

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

Код - C#: [Выделить]
  1.   /// <summary>
  2.   /// Создает круг
  3.   /// заданного радиуса в указанной точке.
  4.   /// </summary>
  5.   DetailArc CreateCircle(
  6.     Document doc,
  7.     XYZ location,
  8.     double radius )
  9.   {
  10.     XYZ norm = XYZ.BasisZ;
  11.  
  12.     double startAngle = 0;
  13.     double endAngle = 2 * Math.PI;
  14.  
  15.     Plane plane = new Plane( norm, location );
  16.  
  17.     Arc arc = Arc.Create( plane,
  18.       radius, startAngle, endAngle );
  19.  
  20.     return doc.Create.NewDetailCurve(
  21.       doc.ActiveView, arc ) as DetailArc;
  22.   }

Внешняя команда содержит следующие шаги:

  • Просим пользователя выбрать кривую
  • Проверим валидность этой кривой
  • Извлечем необходимые данные из выбранной кривой
  • Создадим список точек, равноудаленных друг от друга вдоль этой кривой
  • Поставим метку в каждой точке из списка

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

Код - C#: [Выделить]
  1. public Result Execute(
  2.   ExternalCommandData commandData,
  3.   ref string message,
  4.   ElementSet elements )
  5. {
  6.   UIApplication uiapp = commandData.Application;
  7.   UIDocument uidoc = uiapp.ActiveUIDocument;
  8.   Document doc = uidoc.Document;
  9.  
  10.   Reference r = null;
  11.  
  12.   try
  13.   {
  14.     r = uidoc.Selection.PickObject(
  15.       ObjectType.Element,
  16.       new CurveSelectionFilter(),
  17.       "Выберите дугу или сплайн" );
  18.   }
  19.   catch( Autodesk.Revit.Exceptions
  20.     .OperationCanceledException )
  21.   {
  22.     return Result.Cancelled;
  23.   }
  24.  
  25.   if( null == r )
  26.   {
  27.     message = "Не выбрана кривая";
  28.     return Result.Failed;
  29.   }
  30.  
  31.   Element e = doc.GetElement( r );
  32.  
  33.   if( null == e || !( e is CurveElement ) )
  34.   {
  35.     message = "Выбранный элемент не является кривой";
  36.     return Result.Failed;
  37.   }
  38.  
  39.   // Извлекаем данные из выбранной кривой.
  40.  
  41.   Curve curve = ( e as CurveElement ).GeometryCurve;
  42.  
  43.   IList<XYZ> tessellation = curve.Tessellate();
  44.  
  45.   // Создаем список равноудаленных точек.
  46.  
  47.   List<XYZ> pts = new List<XYZ>( 1 );
  48.  
  49.   double stepsize = 5.0;
  50.   double dist = 0.0;
  51.  
  52.   XYZ p = curve.GetEndPoint( 0 );
  53.  
  54.   foreach( XYZ q in tessellation )
  55.   {
  56.     if( 0 == pts.Count )
  57.     {
  58.       pts.Add( p );
  59.       dist = 0.0;
  60.     }
  61.     else
  62.     {
  63.       dist += p.DistanceTo( q );
  64.  
  65.       if( dist >= stepsize )
  66.       {
  67.         pts.Add( q );
  68.         dist = 0;
  69.       }
  70.       p = q;
  71.     }
  72.   }
  73.  
  74.   // Помечаем кружком точку на линии.
  75.  
  76.   using( Transaction tx = new Transaction( doc ) )
  77.   {
  78.     tx.Start( "Рисуем круги на линии" );
  79.  
  80.     foreach( XYZ pt in pts )
  81.     {
  82.       CreateCircle( doc, pt, 1 );
  83.     }
  84.     tx.Commit();
  85.   }
  86.   return Result.Succeeded;
  87. }

В текущей реализации не поддерживаются прямые линии, так как метод Tessellate возвращает только две точки – начало и конец линии.

Вот такой я получил результат на вашей тестовой модели.

 

Надеюсь я ответил на ваш вопрос.

Здесь вы можете скачать полный исходный код надстройки.

Источник: http://thebuildingcoder.typepad.com/blog/2013/11/placing-equidistant-points-along-a-curve.html

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

Опубликовано 12.11.2013
Отредактировано 12.11.2013 в 08:03:31