Coordinate Systems in Revit
Coordinate Systems in Revit is not a simple topic, especially with Revit API.
But I want to simplify it for you so you don't spend countless hours on trial and error. And of course I will provide an easy to use python code in the end!
First of all let's have an overview of Coordinate Systems in Revit.
There are 3 systems available:
Internal (Used as the internal system of Revit, used in Revit API)
Project (Based on Project Base Point)
Survey (Based on Survey Point)
Also keep in mind that in Revit we can set True North rotation, which will not affect all systems similar.
Internal - True North Rotation is not applied
Project - True North Rotation is not applied
Survey - All coordinates will be affected by True North Rotation.
Create Tags
I highly encourage you to spend some time and create proper tags for Internal, Project and Survey coordinates so you can see the right values all the time. This will help you understand what happens with your coordinates and compare with results in your code much faster.
π‘Keep in mind that Coordinates are shown as North/East/Elevation instead of XYZ
Coordinates in Revit API
Also keep in mind, that when we work with Revit API we are working with Internal Coordinate System in feet units.
When you get any coordinates or you want to place an element in your project, we work with Internal Coordinates in XYZ class in feet units. So if you want to take a set of Project or Survey points for placing your elements you will need to convert it to Internal to place it correctly.
Now let's dive into code.
Coordinate Converter Class
I've written this PointConverter Class that will convert any units between each other. I hope you will find it useful!
π‘ Note that values for X, Y, Z are provided in meters, and it will be converted into Internal units(feet) inside of the class. Adjust to your own needs if necessary.
__title__ = 'CoordSystems'
from Autodesk.Revit.DB import *
uidoc = __revit__.ActiveUIDocument
doc = __revit__.ActiveUIDocument.Document
def convert_internal_units(value, get_internal=True):
"""Function to convert Internal units to meters or vice versa.
:param value: Value to convert
:param get_internal: True - Convert TO Internal / Flase - Convert FROM Internal
:return: Length in Internal units or Meters."""
if get_internal:
return UnitUtils.ConvertToInternalUnits(value, UnitTypeId.Meters)
return UnitUtils.ConvertFromInternalUnits(value, UnitTypeId.Meters)
class PointConverter:
pt_internal = None
pt_survey = None
pt_project = None
def __init__(self, x, y, z, coord_sys='internal', input_units = 'm'):
""" PointConverter - Convert coordinate into desired coordinate system.
Args:
x : Float in meters representing the x-coordinate.
y : Float in meters representing the y-coordinate.
z : Float in meters representing the z-coordinate.
coord_sys : Coordinate System of provided coordinates.
Possible values: 'internal'/'project'/'survey'
input_units : Float in meters representing the z-coordinate.
Possible Values: 'm' / 'ft' """
srvTrans = self.GetSurveyTransform()
projTrans = self.GetProjectTransform()
if input_units == 'm':
x = convert_internal_units(x, get_internal=True)
y = convert_internal_units(y, get_internal=True)
z = convert_internal_units(z, get_internal=True)
if coord_sys.lower() == 'internal':
self.pt_internal = XYZ(x, y, z)
self.pt_survey = self.ApplyInverseTransformation(srvTrans , self.pt_internal)
self.pt_project = self.ApplyInverseTransformation(projTrans, self.pt_internal)
elif coord_sys.lower() == 'project':
self.pt_project = XYZ(x, y, z)
self.pt_internal = self.ApplyTransformation(projTrans, self.pt_project)
self.pt_survey = self.ApplyInverseTransformation(srvTrans, self.pt_internal)
elif coord_sys.lower() == 'survey':
self.pt_survey = XYZ(x, y, z)
self.pt_internal = self.ApplyTransformation(srvTrans, self.pt_survey)
self.pt_project = self.ApplyInverseTransformation(projTrans, self.pt_internal)
else: raise Exception("Wrong argument value for 'coord_sys' in PointConverter class.")
def GetSurveyTransform(self):
"""Gets the Active Project Locations Transform (Survey)."""
return doc.ActiveProjectLocation.GetTotalTransform()
def GetProjectTransform(self):
"""Get the Project Base Points Transform."""
basePtLoc = next((l for l in FilteredElementCollector(doc) \
.OfClass(ProjectLocation) \
.WhereElementIsNotElementType() \
.ToElements() if l.Name in ['Project', 'Projekt']), None)
return basePtLoc.GetTotalTransform()
def ApplyInverseTransformation(self, t, pt):
"""Applies the inverse transformation of
the given Transform to the given point."""
return t.Inverse.OfPoint(pt)
def ApplyTransformation(self, t, pt):
"""Applies the transformation of
the given Transform to the given point."""
return t.OfPoint(pt)
Author = 'Erik Frits'
How to use it?
The Class is very simple to use.
π‘But please pay attention to units used in your Revit Project and in the Code!
As I mentioned, Revit API always uses Internal units for everything in feet. So pay attention when you work with a set of metric data either when you want to import or export it.
Here is a sample code on how to use this code. Also you will notice that I've added print_coord_in_m
function to help me with displaying the right nubmers when I print it.
def print_coord_in_m(pt, prefix=""):
"""Helper Function to display Point Coordinates
in Meters to compare to Coordinates displayed in Revit."""
x = round(convert_internal_units(pt.X, get_internal=False), 4)
y = round(convert_internal_units(pt.Y, get_internal=False), 4)
z = round(convert_internal_units(pt.Z, get_internal=False), 4)
print(prefix, 'X:{}'.format(x), 'Y:{}'.format(y), 'Z:{}'.format(z))
picked_point = uidoc.Selection.PickPoint()
X = picked_point.X
Y = picked_point.Y
Z = picked_point.Z
converter = PointConverter(X, Y, Z, coord_sys='internal', input_units='ft')
pt_internal = converter.pt_internal
pt_project = converter.pt_project
pt_survey = converter.pt_survey
print_coord_in_m(pt_internal, prefix='Internal: ')
print_coord_in_m(pt_project, prefix='Project: ')
print_coord_in_m(pt_survey, prefix='Survey: ')