Skip to content

pyrevit

pyRevit root level config for all pyrevit sub-modules.

Examples:

from pyrevit import DB, UI
from pyrevit import PyRevitException, PyRevitIOError

# pyrevit module has global instance of the
# _HostAppPostableCommand and _ExecutorParams classes already created
# import and use them like below
from pyrevit import HOST_APP
from pyrevit import EXEC_PARAMS

Attributes

PYREVIT_ADDON_NAME = 'pyRevit' module-attribute

PYREVIT_CLI_NAME = 'pyrevit.exe' module-attribute

VERSION_STRING = version_file.read() module-attribute

matches = re.findall('(\\d+)\\.(\\d+)\\.(\\d+)\\.?(.+)?', VERSION_STRING)[0] module-attribute

BUILD_METADATA = '' module-attribute

VERSION_MAJOR = int(VERSION_MAJOR) module-attribute

VERSION_MINOR = int(VERSION_MINOR) module-attribute

VERSION_PATCH = int(VERSION_PATCH) module-attribute

HOME_DIR = op.dirname(op.dirname(op.dirname(__file__))) module-attribute

DOTNET_RUNTIME_ID = 'netcore' if compat.NETCORE else 'netfx' module-attribute

ROOT_BIN_DIR = op.join(HOME_DIR, 'bin') module-attribute

BIN_DIR = op.join(ROOT_BIN_DIR, DOTNET_RUNTIME_ID) module-attribute

MAIN_LIB_DIR = op.join(HOME_DIR, 'pyrevitlib') module-attribute

MISC_LIB_DIR = op.join(HOME_DIR, 'site-packages') module-attribute

MODULE_DIR = op.join(MAIN_LIB_DIR, 'pyrevit') module-attribute

LOADER_DIR = op.join(MODULE_DIR, 'loader') module-attribute

RUNTIME_DIR = op.join(MODULE_DIR, 'runtime') module-attribute

ADDIN_DIR = op.join(LOADER_DIR, 'addin') module-attribute

ENGINES_DIR = eng.EnginePath module-attribute

basestring = str module-attribute

PYREVIT_CLI_PATH = op.join(HOME_DIR, 'bin', PYREVIT_CLI_NAME) module-attribute

TRACEBACK_TITLE = 'Traceback:' module-attribute

HOST_APP = _HostApplication() module-attribute

EXEC_PARAMS = _ExecutorParams() module-attribute

DOCS = _DocsGetter() module-attribute

USER_SYS_TEMP = os.getenv('temp') module-attribute

USER_DESKTOP = op.expandvars('%userprofile%\\desktop') module-attribute

EXTENSIONS_DEFAULT_DIR = op.join(HOME_DIR, 'extensions') module-attribute

PYREVIT_FILE_PREFIX_UNIVERSAL = '{}_'.format(PYREVIT_ADDON_NAME) module-attribute

PYREVIT_FILE_PREFIX_UNIVERSAL_REGEX = '^' + PYREVIT_ADDON_NAME + '_(?P<fname>.+)' module-attribute

PYREVIT_FILE_PREFIX = '{}_{}_'.format(PYREVIT_ADDON_NAME, HOST_APP.version) module-attribute

PYREVIT_FILE_PREFIX_REGEX = '^' + PYREVIT_ADDON_NAME + '_(?P<version>\\d{4})_(?P<fname>.+)' module-attribute

PYREVIT_FILE_PREFIX_STAMPED = '{}_{}_{}_'.format(PYREVIT_ADDON_NAME, HOST_APP.version, HOST_APP.proc_id) module-attribute

PYREVIT_FILE_PREFIX_STAMPED_REGEX = '^' + PYREVIT_ADDON_NAME + '_(?P<version>\\d{4})_(?P<pid>\\d+)_(?P<fname>.+)' module-attribute

PYREVIT_FILE_PREFIX_UNIVERSAL_USER = '{}_{}_'.format(PYREVIT_ADDON_NAME, HOST_APP.username) module-attribute

PYREVIT_FILE_PREFIX_UNIVERSAL_USER_REGEX = '^' + PYREVIT_ADDON_NAME + '_(?P<user>.+)_(?P<fname>.+)' module-attribute

PYREVIT_FILE_PREFIX_USER = '{}_{}_{}_'.format(PYREVIT_ADDON_NAME, HOST_APP.version, HOST_APP.username) module-attribute

PYREVIT_FILE_PREFIX_USER_REGEX = '^' + PYREVIT_ADDON_NAME + '_(?P<version>\\d{4})_(?P<user>.+)_(?P<fname>.+)' module-attribute

PYREVIT_FILE_PREFIX_STAMPED_USER = '{}_{}_{}_{}_'.format(PYREVIT_ADDON_NAME, HOST_APP.version, HOST_APP.username, HOST_APP.proc_id) module-attribute

PYREVIT_FILE_PREFIX_STAMPED_USER_REGEX = '^' + PYREVIT_ADDON_NAME + '_(?P<version>\\d{4})_(?P<user>.+)_(?P<pid>\\d+)_(?P<fname>.+)' module-attribute

ALLUSER_PROGRAMDATA = PyRevitLabsCommon.PyRevitLabsConsts.PyRevitProgramDataPath module-attribute

USER_ROAMING_DIR = PyRevitLabsCommon.PyRevitLabsConsts.PyRevitPath module-attribute

PYREVIT_ALLUSER_APP_DIR = ALLUSER_PROGRAMDATA module-attribute

PYREVIT_APP_DIR = USER_ROAMING_DIR module-attribute

PYREVIT_VERSION_APP_DIR = op.join(PYREVIT_APP_DIR, HOST_APP.version) module-attribute

THIRDPARTY_EXTENSIONS_DEFAULT_DIR = op.join(PYREVIT_APP_DIR, 'Extensions') module-attribute

Classes

PyRevitException

Bases: Exception

Common base class for all pyRevit exceptions.

Parameters args and message are derived from Exception class.

Attributes

msg property

Return exception message.

PyRevitIOError

Bases: PyRevitException

Common base class for all pyRevit io-related exceptions.

Attributes

msg property

Return exception message.

PyRevitCPythonNotSupported(feature_name)

Bases: PyRevitException

Exception for features not supported under CPython.

Source code in pyrevitlib/pyrevit/__init__.py
def __init__(self, feature_name):
    super(PyRevitCPythonNotSupported, self).__init__()
    self.feature_name = feature_name

Attributes

feature_name = feature_name instance-attribute
msg property

Return exception message.

_HostApplication()

Bases: object

Private Wrapper for Current Instance of Revit.

Provides version info and comparison functionality, alongside providing info on the active screen, active document and ui-document, available postable commands, and other functionality.

Note

Every accessor resolves through the __revit__ builtin, which pyRevit always injects as a UI.UIApplication - in commands and in event hooks alike, including DB-only Application_* hooks. It is None when no UI handle can be resolved, including early host startup. Read accessors return None or empty collections without a host; version comparisons return False. Operations that change UI state still require a usable host. The type checks tolerate third-party hosts that bind __revit__ themselves.

Note

uidoc, doc and active_view are also None while a DB-only event hook is running: the UIApplication is live, but Revit does not guarantee an ActiveUIDocument until the event returns. Read the document the event carries from EXEC_PARAMS.event_doc.

Examples:

hostapp = _HostApplication()
hostapp.is_newer_than(2022)
Source code in pyrevitlib/pyrevit/__init__.py
def __init__(self):
    self._postable_cmds = []

Attributes

uiapp property

Return UIApplication provided to the running command, or None.

app property

Return Application provided to the running command, or None.

addin_id property

Return active addin id.

has_api_context property

Determine if host application is in API context.

uidoc property

Return active UIDocument, or None when there is no UI context.

Note

getattr alone only swallows AttributeError, but the handle is real in DB-only event hooks where Revit can refuse ActiveUIDocument outright. That refusal is the same condition as having no active document, so it degrades to None instead of propagating an API exception out of a hook.

doc property

Return active Document.

active_view property writable

Return view that is active (UIDocument.ActiveView).

docs property

Return :obj:list of open :obj:Document objects.

available_servers property

Return :obj:list of available Revit server names.

version property

str: Return version number (e.g. '2018').

subversion property

str: Return subversion number (e.g. '2018.3').

version_name property

str: Return version name (e.g. 'Autodesk Revit 2018').

build property

str: Return build number (e.g. '20170927_1515(x64)').

serial_no property

str: Return serial number number (e.g. '569-09704828').

pretty_name property

Returns the pretty name of the host.

Examples:

Autodesk Revit 2019.2 build: 20190808_0900(x64)

Returns:

Type Description
str

Pretty name of the host

is_demo property

bool: Determine if product is using demo license.

language property

str: Return language type (e.g. 'LanguageType.English_USA').

username property

str: Return the username from Revit API (Application.Username).

proc property

System.Diagnostics.Process: Return current process object.

proc_id property

int: Return current process id.

proc_name property

str: Return current process name.

proc_path property

str: Return file path for the current process main module.

proc_window property

intptr: Return handle to current process window.

proc_screen property

intptr: Return handle to screen hosting current process.

proc_screen_workarea property

System.Drawing.Rectangle: Return screen working area.

proc_screen_scalefactor property

float: Return scaling for screen hosting current process.

Methods:

is_newer_than(version, or_equal=False)

bool: Return True if host app is newer than provided version.

Parameters:

Name Type Description Default
version str or int

version to check against.

required
or_equal bool

Whether to include version in the comparison

False
Source code in pyrevitlib/pyrevit/__init__.py
def is_newer_than(self, version, or_equal=False):
    """bool: Return True if host app is newer than provided version.

    Args:
        version (str or int): version to check against.
        or_equal (bool): Whether to include `version` in the comparison
    """
    if self.version is None:
        return False
    if or_equal:
        return int(self.version) >= int(version)
    else:
        return int(self.version) > int(version)
is_older_than(version)

bool: Return True if host app is older than provided version.

Parameters:

Name Type Description Default
version str or int

version to check against.

required
Source code in pyrevitlib/pyrevit/__init__.py
def is_older_than(self, version):
    """bool: Return True if host app is older than provided version.

    Args:
        version (str or int): version to check against.
    """
    return self.version is not None and int(self.version) < int(version)
is_exactly(version)

bool: Return True if host app is equal to provided version.

Parameters:

Name Type Description Default
version str or int

version to check against.

required
Source code in pyrevitlib/pyrevit/__init__.py
def is_exactly(self, version):
    """bool: Return True if host app is equal to provided version.

    Args:
        version (str or int): version to check against.
    """
    return self.version is not None and int(self.version) == int(version)
get_postable_commands()

Return list of postable commands.

Returns:

Type Description
list[_HostAppPostableCommand]

postable commands.

Source code in pyrevitlib/pyrevit/__init__.py
def get_postable_commands(self):
    """Return list of postable commands.

    Returns:
        (list[_HostAppPostableCommand]): postable commands.
    """
    # if list of postable commands is _not_ already created
    # make the list and store in instance parameter
    if self.uiapp is None:
        return []
    if not self._postable_cmds:
        for pc in UI.PostableCommand.GetValues(UI.PostableCommand):
            try:
                rcid = UI.RevitCommandId.LookupPostableCommandId(pc)
                self._postable_cmds.append(
                    # wrap postable command info in custom namedtuple
                    _HostAppPostableCommand(
                        name=safe_strtype(pc),
                        key=rcid.Name,
                        id=rcid.Id,
                        rvtobj=rcid,
                    )
                )
            except Exception:
                # if any error occured when querying postable command
                # or its info, pass silently
                pass

    return self._postable_cmds
post_command(command_id)

Request Revit to run a command.

Parameters:

Name Type Description Default
command_id str

command identifier e.g. ID_REVIT_SAVE_AS_TEMPLATE

required
Source code in pyrevitlib/pyrevit/__init__.py
def post_command(self, command_id):
    """Request Revit to run a command.

    Args:
        command_id (str): command identifier e.g. ID_REVIT_SAVE_AS_TEMPLATE
    """
    command_id = UI.RevitCommandId.LookupCommandId(command_id)
    self.uiapp.PostCommand(command_id)

Functions:

dedupe_sys_path()

Drop repeated entries from sys.path, keeping the first occurrence.

sys.path is a resolution order, not a set: on a duplicated entry the first match wins, so a stale copy earlier in the list shadows a corrected one later and an updated module silently keeps loading from the old folder. A folder is therefore only ever useful once.

Note: some appends happen behind Python's back - IronPython's clr.AddReferenceToFileAndPath puts the referenced assembly's folder on sys.path itself, once per call - so insertion-time dedupe alone cannot guarantee the invariant and this pass is still required.

Important: the first occurrence is kept, so module resolution priority is unchanged by the pass.

Source code in pyrevitlib/pyrevit/__init__.py
def dedupe_sys_path():
    """Drop repeated entries from ``sys.path``, keeping the first occurrence.

    ``sys.path`` is a resolution order, not a set: on a duplicated entry the
    first match wins, so a stale copy earlier in the list shadows a corrected
    one later and an updated module silently keeps loading from the old folder.
    A folder is therefore only ever useful once.

    Note: some appends happen behind Python's back - IronPython's
    ``clr.AddReferenceToFileAndPath`` puts the referenced assembly's folder on
    ``sys.path`` itself, once per call - so insertion-time dedupe alone cannot
    guarantee the invariant and this pass is still required.

    Important: the first occurrence is kept, so module resolution priority is
    unchanged by the pass.
    """
    seen = set()
    duplicates = []
    for index, entry in enumerate(sys.path):
        if not isinstance(entry, _TEXT_TYPES) or not entry:
            continue
        key = _sys_path_key(entry)
        if key in seen:
            duplicates.append(index)
        else:
            seen.add(key)
    for index in reversed(duplicates):
        del sys.path[index]

add_to_sys_path(*paths)

Append folders to sys.path, skipping any folder already on it.

Every pyRevit code path that contributes a folder goes through here, so a folder lands on sys.path once however many times it is contributed - directly, from a library extension, or as a side effect of referencing an assembly. Paths that are empty or that do not resolve to a directory are skipped: they can never satisfy an import, and a stale one shadows nothing while making the path list lie about what is actually searchable.

Parameters:

Name Type Description Default
*paths str

folders to make importable, in priority order.

()

Note: existing entries are never reordered or removed, so appending a folder that is already present leaves the resolution order untouched.

Source code in pyrevitlib/pyrevit/__init__.py
def add_to_sys_path(*paths):
    """Append folders to ``sys.path``, skipping any folder already on it.

    Every pyRevit code path that contributes a folder goes through here, so a
    folder lands on ``sys.path`` once however many times it is contributed -
    directly, from a library extension, or as a side effect of referencing an
    assembly. Paths that are empty or that do not resolve to a directory are
    skipped: they can never satisfy an import, and a stale one shadows nothing
    while making the path list lie about what is actually searchable.

    Args:
        *paths (str): folders to make importable, in priority order.

    Note: existing entries are never reordered or removed, so appending a
    folder that is already present leaves the resolution order untouched.
    """
    keys = set(
        _sys_path_key(entry)
        for entry in sys.path
        if isinstance(entry, _TEXT_TYPES) and entry
    )
    for path in paths:
        if not isinstance(path, _TEXT_TYPES) or not path:
            continue
        if not op.isdir(path):
            continue
        key = _sys_path_key(path)
        if key in keys:
            continue
        keys.add(key)
        sys.path.append(path)