Skip to main content

rvtkit — Pythonic wrapper for the Autodesk Revit API (2025 - 2027)

pypi python license

A Pythonic wrapper around the Autodesk Revit API with idiomatic ergonomics and a LINQ-style query layer. Targets Revit 2025 / 2026 / 2027 (.NET 8+ / CoreCLR) on CPython 3.12 via pythonnet.

The core needs only pythonnet and cloudpickle ; windows, web pages, dashboards, graphs and exports are optional extras (see Installation).

import rvtkit as rk

walls = rk.activeDoc.categories['Walls'].instances

Formerly revit. The package was renamed rvtkit (October 2026) and import revit no longer works : write import rvtkit as rk.

Autodesk and Revit are registered trademarks of Autodesk, Inc. rvtkit is not affiliated with, sponsored or endorsed by Autodesk.


Table of contents


Compatibility

Revit versions

Revit Runtime Bootstrap call Status
2025 CPython 3.12 + pythonnet (.NET 8) loadAssemblies("2025") ✅ runtime-validated by author
2026 CPython 3.12 + pythonnet (.NET 8) loadAssemblies("2026") ✅ primary target — 544 tests in CI
2027 CPython 3.12 + pythonnet (.NET 10) loadAssemblies("2027") ✅ runtime-validated by author

Revit 2025+ runs on .NET 8 (2027 on .NET 10) / CoreCLR and requires CPython 3.12 + pythonnet 3.x. See rvtkit.bootstrap and the embedded runtime configs in rvtkit/_runtimeconfigs/.

Note: Revit 2020 - 2024 (.NET Framework 4.8 / IronPython) is no longer supported as of 0.1.3. Pin to 0.1.2 if you need legacy Revit support.

Launchers

Launcher Injected variable Status
pyRevit (CPython mode) __revit__ ✅
Custom .NET addin / shell host (CPython 3.12) __uiapp__ / __revit__ ✅
RevitPythonShell __revit__ ❌ IronPython only — not supported since 0.1.3

Requirements

  • Revit 2025, 2026, or 2027 (.NET 8+ / CoreCLR host)
  • CPython 3.12 or 3.13 (requires-python = ">=3.12,<3.14" enforced by pip)
  • pythonnet >= 3.0 — installed automatically by pip install on Windows (declared as pythonnet>=3.0; sys_platform == 'win32', so Linux/macOS CI runs skip it)
  • cloudpickle >= 3.0 — installed automatically (the remote mode, rvtkit.connect(), sends functions to Revit with it)
  • Everything else is an optional extra — see Installation

External dependencies (host-provided, not on PyPI)

Pulled in at runtime via clr / pythonnet from whichever Revit version is currently running.

Dependency Source Used for
Autodesk.Revit.DB Revit install Core API — required everywhere
Autodesk.Revit.UI Revit install Dialogs, selection helpers (lazy import)
clr pythonnet .NET interop (assembly loading)
System.Reflection, System.Runtime.Loader .NET BCL Bootstrap (assembly resolver)
System.Collections.Generic, System.IO, System.Xml, System.Drawing.Imaging, System.Diagnostics .NET BCL Various wrappers, lazy-loaded

Blender (rvtkit.blender) and ParaView (rvtkit.paraview) bring their own Python : the bridges run inside them.


Installation

pip install rvtkit

The distribution and the import are both named rvtkit (pip install rvtkit, import rvtkit).

The core is light : what needs a heavier library is an extra, installed with pip install "rvtkit[qt,web]" (several at once) or pip install "rvtkit[all]".

Extra Installs For
qt PySide6, tomli-w windows with widgets (rvtkit.app), themes and icons (rvtkit.ui), the Revit widgets of Qt Designer, the Qt 3D viewer, revit-ui, revit-plugin
web pywebview a web page as the interface (rvtkit.web, revit-web), a dashboard's desktop window
dash dash, dash-bootstrap-components, plotly, pandas live dashboards (rvtkit.dashkit, revit-dash)
pandas pandas .to_df() and the other DataFrame exports
excel pandas, openpyxl Schedule.to_excel()
bi pandas, pyarrow, duckdb rvtkit.dashboard : Parquet / DuckDB for Power BI, Tableau…
mep networkx, pyvis, pydot MEP networks as graphs
marimo marimo rvtkit.editor, the bulk parameter editor
animation imageio[ffmpeg], pillow rvtkit.Recorder (MP4, GIF, WebP)
gltf pygltflib reading external .gltf files in the 3D viewer
console qtconsole, ipykernel the embedded Python console widget
ai rank_bm25, mcp rvtkit.ai : corpus search and the revit-mcp server
all all of the above

The 3D viewer runs xeokit (AGPL-3.0), which rvtkit does not ship : the first viewer that needs it downloads it once from jsDelivr into %LOCALAPPDATA%\rvtkit\xeokit and works offline afterwards.

Configuring the host inside Revit

In your startup script (pyRevit CPython mode, or a custom CPython addin/shell host):

import rvtkit
rvtkit.init()                # walks the call stack to find __revit__ or __uiapp__

init() is also called automatically when you import rvtkit, so in most hosts (RPS console, pyRevit, custom addin where __revit__ lives in globals or builtins) you don't need to call it explicitly. Re-call it after a rvtkit.reload() or when entering a new scope where the host changed.

If no host is injected anywhere on the call stack, fall back to the explicit form:

import rvtkit
rvtkit.set_host(__revit__)   # or __uiapp__ depending on the launcher

Package layout

revit/
├── __init__.py       # getApp / getDoc / getDocs / set_host / init / Print / run_test
├── _host.py          # UIApplication resolution
├── _accessors.py     # RevitContextError + import boundaries
├── _app.py           # Application (renamed from app.py in 0.1.5)
├── _print.py         # rvtkit.Print() — package banner
├── _runtest.py       # rvtkit.run_test() — in-Revit functional check
├── _wrappers.py      # ElementWrapper (base) + parameter access
├── doc.py            # Document  ← main hub (carved into _doc_* mixins)
├── category.py       # CategoryWrapper
├── family.py         # FamilyWrapper / SystemFamilyWrapper / SystemFamilyTypeWrapper
├── elements.py       # FamilyTypeWrapper / InstanceWrapper / ViewWrapper
└── linq/
    ├── __init__.py   # exposes LinqList, LinqMap (aliased as List, Map)
    ├── linqlist.py   # LinqList[T]      — chainable collection
    ├── linqmap.py    # LinqMap[K, V]    — chainable dict (returned by GroupBy)
    └── _exporters.py # JSON / XML / TOML / YAML writers

Documentation

Most doc-level collections (materials, views, sheets, families, familyTypes, categories, levels, grids, phases, worksets, viewTemplates, scopeBoxes, filters, titleBlocks, revisions, …) are subscriptable by name — prefer doc.categories['Walls'] over doc.getCategory('Walls'). Both forms exist; the bracket form is more idiomatic and keeps queries terse.

Quick start

import rvtkit

app = rvtkit.getApp()
doc = rvtkit.getDoc()

Live proxies

Three module-level attributes track the current host state, so user code doesn't have to re-call getApp() / getDoc() on every line:

rvtkit.app          # cached Application (single instance per process)
rvtkit.activeDoc    # live proxy that always forwards to the active Document
rvtkit.activeView   # live proxy that always forwards to the active view

rvtkit.app is resolved on first access (needs the .NET assemblies loaded) and memoised. rvtkit.activeDoc / rvtkit.activeView are live forwarders — they re-resolve the active doc/view on every attribute access, so they remain correct after the user switches documents in Revit:

rvtkit.activeDoc.title                   # current document title
rvtkit.activeDoc.materials['Aluminium']  # current document materials
rvtkit.activeView.name                   # current view name
rvtkit.app.version_name                  # 'Autodesk Revit 2026'

Migration (0.1.4): the old rvtkit.doc / rvtkit.view proxies were renamed to rvtkit.activeDoc / rvtkit.activeView — the old names were shadowed by the rvtkit.doc / rvtkit.view submodules at first lazy import. Update any pre-0.1.4 user code accordingly.

Document

doc = rvtkit.getDoc()

doc.title                          # file name
doc.path                           # full path
doc.is_workshared                  # True/False
doc.view                           # active view (alias for activeView)

doc.categories['Walls']            # CategoryWrapper
doc.families['Basic Wall']         # SystemFamilyWrapper
doc.families['M_Fixed']            # FamilyWrapper (loadable family)
doc.familyTypes['Generic - 200mm'] # FamilyType wrapper (loadable + system)
doc.materials['Aluminium']         # MaterialWrapper
doc.levels['Level 1']              # Level wrapper
doc.getElementById(123456)         # wrapper from an integer id

doc.<collection> returns a LinqList you can iterate, filter, or look up by name. See doc.py for the full list.

Categories

cat = doc.categories['Walls']     # or 'Doors', 'Floors', etc.

cat.name                          # 'Walls'
cat.getFamilies()                 # LinqList[FamilyWrapper | SystemFamilyWrapper]
cat.getFamilyTypes()              # LinqList[FamilyTypeWrapper | SystemFamilyTypeWrapper]
cat.getInstances()                # LinqList[ElementWrapper | InstanceWrapper]

Loadable / system detection is automatic and dynamic — no hard-coded list. Works for every Revit category (architecture, MEP, structure, etc.).

Families

# Loadable
fam = doc.families['M_Fixed']
fam.name, fam.getFamilyTypes(), fam.getInstances()

# System (walls, floors, roofs…)
fam = doc.families['Basic Wall']
fam.getFamilyTypes()[0]           # first type via index

Family types

ft = doc.familyTypes['Generic - 200mm']

ft.name, ft.family_name, ft.id

# Parameter access — by name, with unit conversion helpers
ft['Width']                       # raw value (Revit internal feet)
ft.mm('Width')                    # in millimetres
ft.m('Width')                     # in metres
ft.params                         # LinqList of ParamWrapper, subscriptable
ft.params['Width'].value          # explicit form

ft.getInstances()                 # LinqList[ElementWrapper] of this type

Instances / elements

inst = doc.categories['Doors'].getInstances().First()

inst.id, inst.name, inst.category, inst.family_name
inst.family_type                  # FamilyTypeWrapper (navigable)
inst.family                       # FamilyWrapper

# Parameters — subscriptable, both read and write
inst['Mark']                      # read
inst.params['Mark'] = 'W1'        # write (transaction implicit when none active)
inst.mm('Height')
inst.location                     # (x, y, z) or LocationCurve

LinqList — operators

LinqList is returned by every collection method. Intermediate operations are lazy (evaluated on demand).

Category Operators
Filter / project Where Select SelectMany Distinct Partition
Order OrderBy OrderByDescending Reverse
Slice Take Skip TakeWhile SkipWhile
Set algebra Union Intersect Except Concat Zip Aggregate (also | & - +)
Itertools Pairwise Window(n) Chunk(n)
Group GroupBy → LinqMap ; CountBy → LinqMap[K, int]
Joining Join (INNER) GroupJoin DefaultIfEmpty
Terminals First FirstOrDefault Last Single Any All Count Sum Min Max Average MinBy MaxBy Contains
Convert ToList ToDict
Side effects ForEach Tap Print
Revit shortcut Instances (= SelectMany(t => t.getInstances()))
Pandas (QA / reporting) to_df query pivot_table value_counts describe
# Typical chain
doc.categories['Walls'].getFamilyTypes() \
   .Where(lambda t: (t.mm('Width') or 0) > 200) \
   .OrderByDescending(lambda t: t.mm('Width')) \
   .Take(5) \
   .Select(lambda t: t.name)

QA / reporting via pandas

For BIM QA audits and reporting deliverables, LinqList exposes a pandas-borrowed shortcut layer. query() returns a LinqList[Wrapper] (not a DataFrame), so chaining stays natural; pivot_table / value_counts / describe produce ready-to-export analytical outputs.

walls = doc.categories['Walls'].instances

# SQL-like filter, round-trip to wrappers
walls.query("Area > 50 and not `Fire Rating`") \
     .to_df(['Mark', 'Level', 'Area']) \
     .to_excel('audit_CF.xlsx')

# Pivot — area per level × wall type
walls.pivot_table('Area', index='Level',
                  columns='Type', aggfunc='sum')

Requires pip install rvtkit[pandas].

LinqMap — operators

LinqMap is returned by GroupBy(). Each value is a LinqList.

groups = doc.categories['Walls'].getFamilyTypes() \
            .GroupBy(lambda t: t.family_name)

groups['Basic Wall']                           # LinqList of Basic Wall types
groups.Keys()                                  # LinqList of keys
groups.Where(lambda k, v: v.Count() > 2)       # filter the groups
groups.Select(lambda k, v: (k, v.Count()))     # project → LinqList of tuples
groups.Print(lambda k, v: '{}: {}'.format(k, v.Count()))
groups.Flatten()                               # flat LinqList of every element

LinqMap is Generic[K, V], so the k and v lambda parameters carry the right types end-to-end — see the next section.

IDE autocomplete (typed lambdas)

LinqList and LinqMap are generic (LinqList[T] / LinqMap[K, V]), and the doc-level collections expose typed return signatures, so Pylance, Pyright, Jedi, and PyCharm propose wrapper members on lambda parameters:

rvtkit.activeDoc.materials.Where(lambda m: m.<TAB>)         # → MaterialWrapper members
rvtkit.activeDoc.getElements().Select(lambda e: e.<TAB>)    # → ElementWrapper members

# Group + filter — k is str, v is LinqList[ElementWrapper]
rvtkit.activeDoc.getElements() \
    .GroupBy(lambda e: e.category.name) \
    .Where(lambda k, v: k.<TAB>)                           # → str members

Subscript chains preserve the wrapper type too:

rvtkit.activeDoc.materials['Aluminium'].thermal.<TAB>       # → ThermalWrapper members
rvtkit.activeDoc.familyTypes['Generic - 200mm'].mm('<TAB>') # parameter helpers

Context managers

Two with-blocks save/restore Revit UI state automatically — even when the body raises:

# Save the current selection, set a new one, restore on exit
with rvtkit.activeDoc.selectionScope(walls):
    ...   # selection restored, even on exception

# Temporarily isolate elements in a view, restore full visibility on exit
with rvtkit.activeView.isolated(walls):
    rvtkit.activeView.Print()
    ...

isolated() uses IsolateElementsTemporary (session-only, not persisted on save) but is still wrapped in a SubTransaction so it composes with any outer TransactionWrapper.

Jupyter / pyRevit Output rendering

LinqList and LinqMap both implement _repr_html_, so they render as proper tables in Jupyter notebooks and in the pyRevit Output window. No explicit call needed — just leave the value as the last expression of a cell:

rvtkit.activeDoc.materials \
    .Where(lambda m: m.thermal is not None) \
    .Select(lambda m: (m.name, m.thermal.thermalConductivity))
# → HTML table in Jupyter / pyRevit Output, plain repr in a terminal

Diagnostics & banners

Three Print() surfaces describe what the package sees, at three different scopes:

rvtkit.Print()              # the package itself — version, install path,
                           # Python runtime, host status, loaded submodules.
                           # Works *before* a host is bound (handy for
                           # diagnosing import problems in plain CPython).

rvtkit.app.Print()          # the running Revit host — VersionName, build,
                           # user, # documents open, active doc, memory,
                           # PID, uptime.

rvtkit.activeDoc.Print()    # the active document — title, path, workshared
                           # status, # elements, # families, etc.

Other diagnostics:

rvtkit.submodules           # sorted LinqList[str] of loaded rvtkit.* modules
rvtkit.reload()             # force-reload every rvtkit.* submodule
                           # (returns LinqList[str] of reloaded names)
rvtkit.run_test()           # in-Revit functional check — PASS/FAIL table
                           # over import / host / doc / proxies / linq
                           # / params / transaction / Print

Errors raised when the package is exercised outside a Revit process (no Autodesk.Revit.DB assemblies, no host bound) come out as a single RevitContextError(RuntimeError) with a two-frame traceback — no seven-deep ModuleNotFoundError chain to wade through:

from rvtkit import RevitContextError
try:
    rvtkit.getDoc()
except RevitContextError as e:
    print('not running inside Revit:', e)

Smart printing

Every wrapper and collection defines its own multi-line __str__ — just use print():

print(doc.families['Basic Wall'])
print(doc.categories['Walls'].getFamilyTypes())

doc.categories['Windows'].getFamilies().Print(lambda f: f.name)

Full examples

import rvtkit
doc = rvtkit.getDoc()

# Walls thicker than 200 mm, sorted desc
doc.categories['Walls'].getFamilyTypes() \
   .Where(lambda t: (t.mm('Width') or 0) > 200) \
   .OrderByDescending(lambda t: t.mm('Width')) \
   .Print(lambda t: '{:40s}  {:>8.1f} mm'.format(t.name, t.mm('Width')))

# Instance count per window family
doc.categories['Windows'].getFamilies() \
   .GroupBy(lambda f: f.name) \
   .Select(lambda k, v: (k, v.Flatten().Count())) \
   .ForEach(lambda pair: print('{}: {} instance(s)'.format(*pair)))

# Navigation from an instance
inst = doc.categories['Doors'].getInstances().First()
print(inst.family_name, inst.family_type.name, inst.family_type.mm('Height'))

Licence

MIT (see LICENSE). The wheel also ships a few third-party files (AvalonDock, ModernWpf, FilterDataGrid, PyQtDarkTheme, Material Symbols, the WebView2 SDK), each under its own licence : see THIRD_PARTY_NOTICES.md, installed with the package's metadata. xeokit (AGPL-3.0), which the 3D viewer runs, is not part of rvtkit : it is downloaded on first use (see Installation).

Metadata

Release files for rvtkit 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rvtkit 0.2.1
File Size Uploaded
rvtkit-0.2.1.tar.gz 8.0 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for rvtkit 0.2.1
File Interpreter ABI Platform
rvtkit-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 16.2 MB

Release files / rvtkit-0.2.1.tar.gz

Download URL rvtkit-0.2.1.tar.gz
Size 8.0 MB
Tags Source
SHA-256 checksum
How to use checksums
76438f185405bc15ebe38e50137c64204bf5c64135fc515867a5512aa3defc00
BLAKE2b-256 checksum
How to use checksums
c3e361bc80c13e3045889614ac37a4a4dc1dc97659a6a1864e1be43197f402b0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release files / rvtkit-0.2.1-py3-none-any.whl

Download URL rvtkit-0.2.1-py3-none-any.whl
Size 8.2 MB
Tags Python 3
SHA-256 checksum
How to use checksums
4e68f012649392f336b60d78957ed3077f3a9a5976888a97d33527b7e8077f4f
BLAKE2b-256 checksum
How to use checksums
416a8e7f85d451f2d743b988501d31eaf5c1a3fbf1a2f0bbecdb389b0a01b650
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page