Convert Internal/Survey/Project Coordinates with Revit API

# -*- coding: utf-8 -*-

# Imports
from Autodesk.Revit.DB import *

# ╦  ╦╔═╗╦═╗╦╔═╗╔╗ ╦  ╔═╗╔═╗
# ╚╗╔╝╠═╣╠╦╝║╠═╣╠╩╗║  ║╣ ╚═╗
#  ╚╝ ╩ ╩╩╚═╩╩ ╩╚═╝╩═╝╚═╝╚═╝ VARIABLES
#--------------------------------------------------
uidoc     = __revit__.ActiveUIDocument
doc       = __revit__.ActiveUIDocument.Document  #type: Document

# ╔═╗╦ ╦╔╗╔╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
# ╠╣ ║ ║║║║║   ║ ║║ ║║║║╚═╗
# ╚  ╚═╝╝╚╝╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ Functions
#--------------------------------------------------
app       = doc.Application
rvt_year  = int(app.VersionNumber)
def convert_internal_units(value, get_internal=True, units='m'):
    # type: (float, bool, str) -> float
    """Function to convert Internal units to meters or vice versa.
    :param value:        Value to convert
    :param get_internal: True to get internal units, False to get Meters
    :param units:        Select desired Units: ['m',]
    :return:             Length in Internal units or Meters."""
    if rvt_year >= 2021:
        if units == 'm': units = UnitTypeId.Meters
    else:
        if units == 'm': units = DisplayUnitType.DUT_METERS
    if get_internal:
        return UnitUtils.ConvertToInternalUnits(value, units)
    return UnitUtils.ConvertFromInternalUnits(value, units)

# ╔═╗╦  ╔═╗╔═╗╔═╗
# ║  ║  ╠═╣╚═╗╚═╗
# ╚═╝╩═╝╩ ╩╚═╝╚═╝ CLASS
#--------------------------------------------------
class PointConverter:
    pt_internal = None
    pt_survey   = None
    pt_project  = None

    def __init__(self, x, y, z, coord_sys='internal'):
        # type:( float, float, float, str) -> None
        """ 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'
        Return: None"""

        # Get Systems Transform
        srvTrans  = self.GetSurveyTransform()
        projTrans = self.GetProjectTransform()

        # Convert to Internal Units
        X = convert_internal_units(x, get_internal=True) # Convert Units to Internal
        Y = convert_internal_units(y, get_internal=True) # Convert Units to Internal
        Z = convert_internal_units(z, get_internal=True) # Convert Units to Internal

        # 1️⃣INTERNAL COORDINATE SYSTEM
        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)

        # 2️⃣PROJECT COORDINATE SYSTEM
        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)

        # 3️⃣SURVEY COORDINATE SYSTEM
        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.")

    #⛑ HELPING METHODS
    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 == "Project"), 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)

# ╔╦╗╔═╗╦╔╗╔
# ║║║╠═╣║║║║
# ╩ ╩╩ ╩╩╝╚╝
# EXAMPLE ON HOW TO USE:

# 😉 Helper Function
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, 'N:{}'.format(y), 'E:{}'.format(x), 'Elev:{}'.format(z))
# --------------------------------------------------------------

# 1️⃣ Define Coordinates
X =  2.8284    # E Coordinate
Y = -4.899     # N Coordinate
Z = 0        # Elevation

# 2️⃣ Create PointConverter instance
converter = PointConverter(X, Y, Z, coord_sys='survey')

# 3️⃣ Converted Points
pt_internal = converter.pt_internal
pt_project  = converter.pt_project
pt_survey   = converter.pt_survey

# 👀 Display Results
print_coord_in_m(pt_internal, prefix='Internal: ')
print_coord_in_m(pt_project, prefix='Project: ')
print_coord_in_m(pt_survey, prefix='Survey: ')

Author = 'Erik Frits'

⌨️ Happy Coding!
Erik Frits