Distances, Switches, Kiss-ing and a Dino

I continued my activity in the Revit API discussion forum and had lots of interesting discussions there.

One recent thread caused me to bring up a favourite topic of mine, on keeping things simple.

I'll also mention two other less recent recurring questions – one of them partly because I went to the effort of creating a nice figure illustrating it   :-)

Distance Between Two Points in a Specific Direction

This question on determining the distance between two points along a given vector has come up several times in the past, so let's capture it here for future reference:

Question: Does the Revit API provide a method of returning the distance between two points along a given vector?

I realize there are ways to manually do this, but I was wondering if the Revit API had something natively to make my code a little cleaner.

Answer 1: You can use a Autodesk.Revit.DB.Line. It provides a double Length property that it inherits from Curve.

Answer 2: Where pointA and pointB are XYZ points:

  dim distance as double = pointA.DistanceTo(pointB)

The XYZ class also has .multiply and .divide functions if you want to go a proportion of that distance along a vector.

Answer 3: If you need the distance of the points projected onto the given vector:

  XYZ p1 = new XYZ( 0, 10, 0 );
  XYZ p2 = new XYZ( 10, 60, 0 );
  XYZ direction = new XYZ( 30, 60, 0 );
  double distance = direction.Normalize().DotProduct( p1.Subtract( p2 ) );
  distance = Math.Abs( distance );

Answer 4: I would award the main prize and cigar to answer 3 above.

Partly for providing what I guess might be the right answer.

Above all for guessing what may or may not be the right intended question.

Is this an accurate description of your needs?

Question rephrased: Given points p1 and p2, what is the distance between them, measured along the line L?

Distance between points in a specified direction

If that is indeed what you need, you are actually not asking about the distance between the points at all.

These two points, together with the direction w of L, define two planes.

You are asking about the distance between those two planes.

Are you?

If so, answer 3 above is absolutely accurate and can be reformulated as:

Answer:

  v = p2 - p1
  w = L.direction.normalise
  distance_along_w = v.dotProduct( w )

Unfortunately, the Revit geometry API is not full-fledged and therefore lacks a method to return the distance between two planes.

Otherwise, that would probably provide an even more straightforward path.

The suggested one is very direct and efficient, though, and hard to beat in those respects.

I hope this helps and that we collectively succeeded at nailing your intention.

Clarification: Why can you not simply use the built-in XYZ.DistanceTo method like this?

  double rDist = p1.DistanceTo( p2 );

Because DistanceTo returns the shortest distance between two points, not the distance along a given direction. The latter is actually not the distance between two points, but the distance between two planes.

Response: Yes, you are absolutely correct in your initial assumption: I was looking for the distance between two planes, my initial question was a very round-about way of getting to that point. Your answers gave me exactly what I was looking for. Putting together a small routine utilising the information I was given gave me A) a nice, recallable routine, B) something that I can tailor to my needs. I realise that measuring between planes isn't something 'native' to Revit, and making my own routines has the benefit of allowing me to make methods for simple functions that I can expand and modify as needed.

Revit Command-Line Switches

Another recurring question is on Revit command line switches, which Matt Taylor just summarised succinctly:

These are the switches I'm aware of and tested in Revit 2017:

Many thanks to Matt and Callumf for providing this list!

Keeping Things Simple with the Revit API

The most interesting issue today is on listing all views in a project on a Winform throwing an exception providing ma an opportunity to wax philosophical on keeping things simple, although Rudi 'Revitalizer' provided the more succinct and accurate fix to the underlying problem:

Question: I am attempting to create a WinForm which lists all the views in the project as a viewTree. The WinForm run command is then packaged into a Dynamo ZeroTouch node, however the views are not displayed when the form launches, and when I relaunch the form I get an error that line 53 (the foreach statement that attempts to use the view name to populate the viewTree) is "not set to an instance of an object". This is my first attempt to raise and consume events and I've done everything I can to get it to work; what is the cause of this error?

The form successfully launches via the dynamo node:

Dynamo node displaying Windows form

The exception:

Exception displaying Windows form

The code:

 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Autodesk.DesignScript.Runtime;
using Autodesk.DesignScript.Interfaces;
using Autodesk.DesignScript.Geometry;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Architecture;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.Attributes;
using RevitServices.Persistence;
using RevitServices.Transactions;
using Revit.Elements;
using Revit.GeometryConversion;

namespace SelectSheetsAndViews
{
  /// <summary> The form class</summary>
  public partial class FormRevitSelect : System.Windows.Forms.Form
  {
    public FormRevitSelect()
    {
      InitializeComponent();
    }

    private void DynamoTreeListSelect_Activated( object sender, System.EventArgs e )
    {

    }

    private void textBox1_TextChanged( object sender, EventArgs e )
    {

    }

    private void tableLayoutPanel1_Paint( object sender, PaintEventArgs e )
    {

    }

    private void Form1_Load( object sender, EventArgs e )
    {
      List<Autodesk.Revit.DB.View> d1 = new ThresholdReachedEventArgs().Views;
      //List<string> d = new List<string> { "A", "B", "C", "D"};
      foreach( Autodesk.Revit.DB.View x in d1 )
      //foreach (string x in d)
      {
        treeView1.Nodes.Add( x.ToString() );
      }
    }

    private void checkedListBox1_SelectedIndexChanged( object sender, EventArgs e )
    {

    }

    private void button1_Click( object sender, EventArgs e )
    {
      this.Close();
    }

    private void treeView1_AfterSelect( object sender, TreeViewEventArgs e )
    {

    }
  }


  /// <summary>A Revit class to get all the sheets and views in the document</summary>
  public class RevitWinForm
  {
    private List<object> _sheetsAndViews;
    private List<object> sheetsAndViewsToDelete;


    internal List<object> GetSheetAndViewsToDelete { get { return sheetsAndViewsToDelete; } }

    private RevitWinForm( List<object> sheetsAndViews )
    {
      _sheetsAndViews = sheetsAndViews;

    }

    internal List<object> GetSheetAndViewList { get { return _sheetsAndViews; } }

    /// <summary>
    /// Function to collect all the views and sheets in the document
    /// </summary> 
    internal static List<Autodesk.Revit.DB.View> SheetsAndView()
    {
      Document doc = DocumentManager.Instance.CurrentDBDocument;
      FilteredElementCollector collector = new FilteredElementCollector( doc ).OfCategory( BuiltInCategory.OST_Views );
      List<Autodesk.Revit.DB.View> viewList = collector.ToElements() as List<Autodesk.Revit.DB.View>;

      return viewList;
    }


    /// <summary>
    /// The MultiReturn attribute can be used to specify
    /// the names of multiple output ports on a node that 
    /// returns a dictionary. The node must return a dictionary
    /// to be recognized as a multi-out node.
    /// </summary>
    /// <param name="refresh">Refresh</param>
    /// <returns>DynamoTreeListSelect</returns>
    public static string CompactDocument( bool refresh )
    {
      System.Windows.Forms.Application.Run( new FormRevitSelect() );
      return "Process Complete";
    }

  }

  class Program
  {
    static void Main( string[] args )
    {
      RevitSheetsAndViews v = new RevitSheetsAndViews();
      v.GetRevitViews += v_ViewsOUT;
    }

    static void v_ViewsOUT( object sender, ThresholdReachedEventArgs e )
    {
      List<Autodesk.Revit.DB.View> d1 = e.Views;
    }
  }

  class RevitSheetsAndViews
  {

    private List<Autodesk.Revit.DB.View> views;

    /*public RevitSheetsAndViews(List<Autodesk.Revit.DB.View> viewsOUT)
    {
      views = viewsOUT;
    }
    */
    public RevitSheetsAndViews()
    {
      Document doc = DocumentManager.Instance.CurrentDBDocument;

      FilteredElementCollector collector = new FilteredElementCollector( doc ).OfCategory( BuiltInCategory.OST_Views );
      views = collector.ToElements() as List<Autodesk.Revit.DB.View>;
      ThresholdReachedEventArgs args = new ThresholdReachedEventArgs();
      args.Views = views;
      RevitViewsEventHandler( args );
    }

    protected virtual void RevitViewsEventHandler( ThresholdReachedEventArgs e ) //raise the EventHandler delegate and associate GetRevitViews to it
    {
      EventHandler<ThresholdReachedEventArgs> handler = GetRevitViews;
      if( handler != null )
      {
        handler( this, e );
      }
    }

    public event EventHandler<ThresholdReachedEventArgs> GetRevitViews; // declare an event named GetRevitViews. The event is associated with the EventHandler delegate and raised in a method named OnThresholdReached.
  }

  public class ThresholdReachedEventArgs : EventArgs
  {
    public List<Autodesk.Revit.DB.View> Views { getset; }
  }
}

Answer 1: The answer to every question in the universe is identical:

KISS!

Unfortunately, that often raises a follow-up question:

How?

How can I simplify my problem?

In programming, simplification can often be achieved by separating separate tasks.

That was one of the main goals of object oriented programming OOP when it was invented or at least started emerging six (!) decades ago.

In this case, you are talking with the Revit API to obtain information about views, e.g., their names, or whatever other information you wish to display.

That is one task.

Another issue, completely separate, is to display that information.

I suggest you separate the two completely.

In other words, talk with the Revit API, obtain the information you require, store it, and stop communicating with Revit.

Then, and only then, proceed with other things, such as displaying your stuff.

Answer 2: The cause is:

When you create a new ThresholdReachedEventArgs instance, its Views property is null, initially.

It must be set before accessing it via a foreach loop.

Response: Thanks, Rudi and Jeremy.

Very helpful advice. I realised one of the exceptions was caused by problems with the way I'd written the element collector. Once I fixed that, I acted upon Jeremys advice and did away with extending the Event Handler class, and simply implemented the collector and the rest of my function within the Load event of the form. All now works as expected:

Dynamo displaying Bimorph Windows form

For anyone interested, here's the working 'after' code, demonstrating the simplification:

 
namespace Revit
{
  /// <summary> The form class</summary>
  internal partial class FormRevitSelect : System.Windows.Forms.Form
  {
    public List<int> _viewId;

    public List<int> GetViewIds
    {
      get { return _viewId; }
      set { _viewId = value; }
    }

    public FormRevitSelect()
    {
      InitializeComponent();
    }

    private void textBox1_TextChanged( object sender, EventArgs e )
    {
    }

    private void Form1_Load( object sender, EventArgs e )
    {
      Document doc = DocumentManager.Instance.CurrentDBDocument;

      BrowserOrganization browserOrg = BrowserOrganization
        .GetCurrentBrowserOrganizationForViews( 
          doc ); //Get the browser item from the document

      FilteredElementCollector collector 
        = new FilteredElementCollector( doc )
          .OfCategory( BuiltInCategory.OST_Views );

      List<Autodesk.Revit.DB.Element> viewList 
        = (List<Autodesk.Revit.DB.Element>) 
          collector.ToElements().ToList();

      foreach( Autodesk.Revit.DB.View v in viewList )
      {
        //v.ViewType.ToString() != "" is used to remove 
        // views which are present in templates by default 
        // but only picked up by the element collector. 
        // They need to be removed from the list.

        if( v.Name != null 
          && v.ViewType.ToString() != "" ) 
        {
          List<FolderItemInfo> folderInfo 
            = browserOrg.GetFolderItems( v.Id )
              .ToList(); //Get the folder information for the view

          treeView1.Nodes.Add( v.Id.ToString(), v.Name );
        }
      }
    }
  }
}

ZeroTouch code for Dynamo library import:

 
/// <summary>A Revit class to get all the 
/// sheets and views in the document</summary>
public class DocumentUtilities
{
  private List<object> _sheetsAndViews;

  internal List<object> GetSheetAndViewsToDelete
  {
    get { return _sheetsAndViews; }
  }

  private DocumentUtilities( List<object> sheetsAndViews )
  {
    _sheetsAndViews = sheetsAndViews;
  }

  /// <summary>
  /// Work in progress
  /// </summary>
  /// <param name="refresh">Refresh to reopen the form</param>
  /// <returns>Revit View Elements</returns>
  public static List<Revit.Elements.Element> 
    ExporttDocument( bool refresh )
  {
    Document doc = DocumentManager.Instance.CurrentDBDocument;

    FormRevitSelect form1 = new FormRevitSelect();
    System.Windows.Forms.Application.Run( form1 );

    List<Revit.Elements.Element> vList 
      = new List<Revit.Elements.Element>();

    foreachint i in form1.GetViewIds )
    {
      Autodesk.Revit.DB.Element v = doc.GetElement(
        new Autodesk.Revit.DB.ElementId( i ) )
          as Autodesk.Revit.DB.Element;

      vList.Add( v.ToDSType( true ) );
    }
    return vList;
  }
}

Run in Dynamo:

Bimorph select

Result:

List views result

Many thanks to Thomas for rasing the issue in the first place, sharing the final result, and Rudi 'Revitalizer' for helping to solve it!

Dino Pondering Lengthening Days

Let me end by pointing out that it is pretty cold and snowy here, even though the days are gradually getting longer again:

Dino pondering the gradual lengthening of daytime

Some warm-blooded creatures prefer to stay inside and interact with the forum instead of pondering nature:

Top solution authors