Converting Revit API C# Code to Python

How do I convert this code sample to Python?

The Revit API documentation and SDK examples are all in C# (and VB). This makes it difficult for those who are just starting to learn how to use the Revit API code with Python.

Porting C# code to Python requires a good understanding of Python, and some familiarity with C#. The guide below is not comprehensive but should cover basic concepts needed to get started.

SharpDevelop Code Conversion Tool

This is helpful to speed up converting a large snippet. I have not tested this extensively, but I used on a few snippets and it seems to do the job although it does not handle imports.

Below is a quick example of how to use it:

  1. Go to the Revit Macro Manager
  2. Start a Module/Macro
  3. Start C# File.
  4. Paste your C# Code.
  5. Tools > Convert Code To > Python

SharConvert

The hard way

Manually porting code by re-writing in Python takes time, but it’s an essential skill to have considering Python is not a supported Revit API language.

Although there are some weird quirks that are not very intuitive, most of it’s vert straight forward and becomes second nature once you have done it a few times.

First review code sample below – a C# function that takes 2 lines as arguments, and returns a point where they intersect. The code also raises an error if an intersection if not found.

(Note: If you are not familiar with function definitions in C#, now might be a good time to read about it here.)

private XYZ GetIntersection( 
  Line line1, 
  Line line2 )
{
  IntersectionResultArray results;
  SetComparisonResult result 
    = line1.Intersect( line2, out results );

  if( result != SetComparisonResult.Overlap ){
    throw new InvalidOperationException( 
      "Input lines did not intersect." );
  }
 
  if( results == null || results.Size != 1 ) {
    throw new InvalidOperationException( 
      "Could not extract line intersection point." );
  }
 
  IntersectionResult iResult = results.get_Item( 0 );

  return iResult.XYZPoint;
}

 

We will break down the function definition line-by-line, but before we start we need to take care of the import statements. Imports are usually not included in C# examples because they work quite differently in C# world.

Start by examining the code to find the objects from the API that are being used (some might not be needed due to the dynamically-typed nature of Python). This step takes some practice and knowledge of how the API is organized, but once you get used to it it’s not bad.

These are the objects I identified in the snippet above:

  • Line
  • XYZ
  • SetComparisonResult
  • IntersectionResultArray
  • InvalidOperationException (you could raise a generic Python exception as well)

And here is the required Python import code (if you are not sure which namespace each class is in, you can search for them on RevitAPIDocs.com or in the SDK.

import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import Line, XYZ, SetComparisonResult, IntersectionResultArray
from Autodesk.Revit.Exceptions import InvalidOperationException

Now let’s look the GetIntersection function definition on line 1 and break it down into parts:

private XYZ GetIntersection( Line line1, Line line2) {

private  Access Modifier – in Python everything is public so it does not have Access Modifiers (public, private, etc). You can always eliminate them.

XYZ  The Return Type. This specifies the return type of the function or method as required in C#. Python is dynamically typed, so return types are also removed.

GetIntersection  Function name. You can keep the same, or name it according to PEP8 which states function names should be snake_case which is generally my preference: get_intersection

Line line1  Argument type and name. Once again, the object type is discarded, keeping only the argument name. The same applies to the second argument Line line2

 

The function definition in Python would look like this:

def get_intersection(line1, line2):

 

Now let’s look at line 5 and 6:

IntersectionResultArray results;  This creates a variable called results of the type IntersectionResultArray. The results variable is explained below. The latter is an array that will hold the line intersections.

SetComparisonResult result = line1.Intersect( line2, out results );  Calls the ‘Intersect’ method of the line object, and store the intersection result which is a SetComparisonResult in the variable result.

The second part is a bit tricky: we know the result variable will store the result intersection. but what it’s up with the out results? out is a way to pass the function an object reference. The object will not be returned by the function, but it will be modified whithin it. To make this possible you have to use the ironpython’s clr.Reference For more information check out the ref and out parameters section of the IronPython documentation.

These 2 lines in Python would look something like this:

results = clr.Reference[IntersectionResultArray]() # create an object Reference called 'results' of the type IntersectionResultArray
result = line1.Intersect(line2, results) # store the result of the intersection operation in the 'result' variable.

After the Intersect method runs, results will contain an array with the intersection points, and result will contain a SetComparisonResult enumeration such as SetComparisonResult.Overlap.

More info about the SetComparisonResult here.

Next, there are conditional statements that checks that everything happened as we expected:

if( result != SetComparisonResult.Overlap ) {
    throw new InvalidOperationException("Input lines did not intersect." );
}

if( results == null || results.Size != 1 ) {
    throw new InvalidOperationException("Could not extract line intersection point." );
}

First we check the result variable is equal to the SetComparisonResult.Overlap enum, and if not, we will raise an exception.

if result != SetComparisonResult.Overlap:
    raise InvalidOperationException('Input lines did not intersect.')

Next, we check if the results is not empty and that is equal to 1 (one intersection point found). If either condition is True, an exception is raised:

if results is None or results.Size != 1:

    raise InvalidOperationException("Could not extract line intersection point." )

In the last part, the intersecting point is extracted from the IntersectionResultArray and returned by the function:

intersectionResult iResult = results.get_Item(0): Create a variable called ‘iResult’ of the type IntersectionResult, and assign it the first item in the results array:

return iResult.XYZPoint; Return the XYZPoint property of the intersectionResult object.

In Python:

intersection = results.Item[0]
return intersection.XYZPoint

# Line can also be written like this:
intersection = results.get_Item(0)

And finally, the entire code together, including a sample function call:

 

import clr
from Autodesk.Revit.DB import Line, XYZ
from Autodesk.Revit.DB import SetComparisonResult, IntersectionResultArray
from Autodesk.Revit.Exceptions import InvalidOperationException

def get_intersection(line1, line2):
  results = clr.Reference[IntersectionResultArray]()		
  result = line1.Intersect(line2, results)
  
  if result != SetComparisonResult.Overlap:
    print('No Intesection')
  
  if results is None or results.Size != 1:
    raise InvalidOperationException("Could not extract line intersection point." )
  
  intersection = results.Item[0]
  return intersection.XYZPoint


line1 = Line.CreateBound(XYZ(0,0,0), XYZ(10,0,0))
line2 = Line.CreateBound(XYZ(5,-5,0), XYZ(5,5,0))
point = get_intersection(line1, line2)
print(point)
# <Autodesk.Revit.DB.XYZ object at 0x00000000000001BA [(5.000000000, 0.000000000, 0.000000000)]>

 

Leave a Reply