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

06/11/2013

Генерация кривой между двумя другими кривыми

Рассмотрим довольно забавную геометрическую проблемку, с которой столкнулся Bang в своем комментарии к статье Кривые в Revit. Довольно активно мы обсуждали как создать кривую ровно посередине между двумя кривыми.

Вопрос: Как можно определить кривую между двумя другими кривыми? Например, между дугой и дугой, кругом и дугой, эллиптической дугой и эллипсом и т.д

Ответ: Интересный у вас вопрос.

В Revit API нет метода, предоставляющего такую функциональность, поэтому придется реализовывать ее самостоятельно.

Тем не менее, класс Curve содержит несколько довольно неплохих обобщенных методов, для доступа к параметрам кривой.

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

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

Гораздо сложнее вычислить точную кривую, лежащую между двумя заданными кривыми.

Этот подход также бы предложен и реализован в обсуждении как создать кривую ровно посередине между двумя кривыми.

Так как мне нравятся подобные геометрические задачки, то я решил поглубже вникнуть в эту проблему и создал новую команду CmdMidCurve в примерах The Building Coder.

Сама команда состоит из трех частей:

  • Выбор двух кривых в пользовательском интерфейсе
  • Определение параметрических данных выбранных кривых
  • Генерация кривой, лежащей посередине между выбранными кривыми

Выбор двух кривых в пользовательском интерфейсе

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

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

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

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

Таким образом я следовал следующему алгоритму:

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

Вот как это выглядит в моей реализации

Код - C#: [Выделить]
  1. [Transaction( TransactionMode.Manual )]
  2. class CmdMidCurve : IExternalCommand
  3. {
  4.   /// <summary>
  5.   /// Number of approximation segments to generate.
  6.   /// </summary>
  7.   const int _nSegments = 64;
  8.  
  9.   const string _prompt
  10.     = "Please run this in a model containing "
  11.     + "exactly two curve elements, and they will be "
  12.     + "automatically selected. Alternatively, pre-"
  13.     + "select two curve elements before launching "
  14.     + "this command, or post-select them when "
  15.     + "prompted.";
  16.  
  17.   /// <summary>
  18.   /// Allow selection of curve elements only.
  19.   /// </summary>
  20.   class CurveElementSelectionFilter : ISelectionFilter
  21.   {
  22.     public bool AllowElement( Element e )
  23.     {
  24.       return e is CurveElement;
  25.     }
  26.  
  27.     public bool AllowReference( Reference r, XYZ p )
  28.     {
  29.       return true;
  30.     }
  31.   }
  32.  
  33.   public Result Execute(
  34.     ExternalCommandData commandData,
  35.     ref string message,
  36.     ElementSet elements )
  37.   {
  38.     UIApplication uiapp = commandData.Application;
  39.     UIDocument uidoc = uiapp.ActiveUIDocument;
  40.     Application app = uiapp.Application;
  41.     Document doc = uidoc.Document;
  42.  
  43.     // Select all model curves in the entire model.
  44.  
  45.     List<CurveElement> curves = new List<CurveElement>(
  46.       new FilteredElementCollector( doc )
  47.         .OfClass( typeof( CurveElement ) )
  48.         .ToElements()
  49.         .Cast<CurveElement>() );
  50.  
  51.     int n = curves.Count;
  52.  
  53.     // If there are less than two,
  54.     // there is nothing we can do.
  55.  
  56.     if( 2 > n )
  57.     {
  58.       message = _prompt;
  59.       return Result.Failed;
  60.     }
  61.  
  62.     // If there are exactly two, pick those.
  63.  
  64.     if( 2 < n )
  65.     {
  66.       // Else, check for a pre-selection.
  67.  
  68.       curves.Clear();
  69.  
  70.       Selection sel = uidoc.Selection;
  71.  
  72.       n = sel.Elements.Size;
  73.  
  74.       Debug.Print( "{0} pre-selected elements.",
  75.         n );
  76.  
  77.       // If two or more model curves were pre-
  78.       // selected, use the first two encountered.
  79.  
  80.       if( 1 < n )
  81.       {
  82.         foreach( Element e in sel.Elements )
  83.         {
  84.           CurveElement c = e as CurveElement;
  85.  
  86.           if( null != c )
  87.           {
  88.             curves.Add( c );
  89.  
  90.             if( 2 == curves.Count )
  91.             {
  92.               Debug.Print( "Found two model curves, "
  93.                 + "ignoring everything else." );
  94.  
  95.               break;
  96.             }
  97.           }
  98.         }
  99.       }
  100.  
  101.       // Else, prompt for an
  102.       // interactive post-selection.
  103.  
  104.       if( 2 != curves.Count )
  105.       {
  106.         curves.Clear();
  107.  
  108.         try
  109.         {
  110.           Reference r = sel.PickObject(
  111.             ObjectType.Element,
  112.             new CurveElementSelectionFilter(),
  113.             "Please pick first model curve." );
  114.  
  115.           curves.Add( doc.GetElement( r.ElementId )
  116.             as CurveElement );
  117.         }
  118.         catch( Autodesk.Revit.Exceptions
  119.           .OperationCanceledException )
  120.         {
  121.           return Result.Cancelled;
  122.         }
  123.  
  124.         try
  125.         {
  126.           Reference r = sel.PickObject(
  127.             ObjectType.Element,
  128.             new CurveElementSelectionFilter(),
  129.             "Please pick second model curve." );
  130.  
  131.           curves.Add( doc.GetElement( r.ElementId )
  132.             as CurveElement );
  133.         }
  134.         catch( Autodesk.Revit.Exceptions
  135.           .OperationCanceledException )
  136.         {
  137.           return Result.Cancelled;
  138.         }
  139.       }
  140.     }
  141.  
  142.     // . . .
  143.  
  144.     return Result.Succeeded;
  145.   }
  146. }

Определение параметрических данных кривых

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

Код - C#: [Выделить]
  1.  // Extract data from the two selected curves.
  2.  
  3.   Curve c0 = curves[0].GeometryCurve;
  4.   Curve c1 = curves[1].GeometryCurve;
  5.  
  6.   double sp0 = c0.GetEndParameter( 0 );
  7.   double ep0 = c0.GetEndParameter( 1 );
  8.   double step0 = ( ep0 - sp0 ) / _nSegments;
  9.  
  10.   double sp1 = c1.GetEndParameter( 0 );
  11.   double ep1 = c1.GetEndParameter( 1 );
  12.   double step1 = ( ep1 - sp1 ) / _nSegments;
  13.  
  14.   Debug.Print( "Two curves' step size [start, end]:"
  15.     + " {0} [{1},{2}] -- {3} [{4},{5}]",
  16.     Util.RealString( step0 ),
  17.     Util.RealString( sp0 ),
  18.     Util.RealString( ep0 ),
  19.     Util.RealString( step1 ),
  20.     Util.RealString( sp1 ),
  21.     Util.RealString( ep1 ) );

Генерация кривой, лежащей между двумя кривыми

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

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

Код - C#: [Выделить]
  1.   // Modify document within a transaction.
  2.  
  3.   using( Transaction tx = new Transaction( doc ) )
  4.   {
  5.     Creator creator = new Creator( doc );
  6.  
  7.     tx.Start( "MidCurve" );
  8.  
  9.     // Current segment start points.
  10.  
  11.     double t0 = sp0;
  12.     double t1 = sp1;
  13.  
  14.     XYZ p0 = c0.GetEndPoint( 0 );
  15.     XYZ p1 = c1.GetEndPoint( 0 );
  16.     XYZ p = Util.Midpoint( p0, p1 );
  17.  
  18.     Debug.Assert(
  19.       p0.IsAlmostEqualTo( c0.Evaluate( t0, false ) ),
  20.       "expected equal start points" );
  21.  
  22.     Debug.Assert(
  23.       p1.IsAlmostEqualTo( c1.Evaluate( t1, false ) ),
  24.       "expected equal start points" );
  25.  
  26.     // Current segment end points.
  27.  
  28.     t0 += step0;
  29.     t1 += step1;
  30.  
  31.     XYZ q0, q1, q;
  32.     Line line;
  33.  
  34.     for( int i = 0; i < _nSegments; ++i, t0 += step0, t1 += step1 )
  35.     {
  36.       q0 = c0.Evaluate( t0, false );
  37.       q1 = c1.Evaluate( t1, false );
  38.       q = Util.Midpoint( q0, q1 );
  39.  
  40.       Debug.Print(
  41.         "{0} {1} {2} {3}-{4} {5}-{6} {7}-{8}",
  42.         i,
  43.         Util.RealString( t0 ),
  44.         Util.RealString( t1 ),
  45.         Util.PointString( p0 ),
  46.         Util.PointString( q0 ),
  47.         Util.PointString( p1 ),
  48.         Util.PointString( q1 ),
  49.         Util.PointString( p ),
  50.         Util.PointString( q ) );
  51.  
  52.       // Create approximating curve segment.
  53.  
  54.       line = Line.CreateBound( p, q );
  55.       creator.CreateModelCurve( line );
  56.  
  57.       p0 = q0;
  58.       p1 = q1;
  59.       p = q;
  60.     }
  61.     tx.Commit();
  62.   }

Тестируемая модель

Вот так выглядит моя тестовая модель

 

Верхняя линия является сплайном, нижняя – простой дугой.

Эти кривые линии совершенно различны по своему типу. Я использую 64 сегмента для построения результирующей кривой.

Результат выглядит вот так:

 

Полный исходный код проекта можно найти в примерах The Building Coder Sample version 2014.0.101.0

Источник: http://thebuildingcoder.typepad.com/blog/2013/08/generating-a-midcurve-between-two-curve-elements.html

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

Опубликовано 06.11.2013
Отредактировано 06.11.2013 в 16:56:42