Skip to main content

Autodesk Revit

Use the VIKTOR Revit worker to run Python scripts on a Windows machine with Revit installed. You can read and change the model that is open in Revit, or start a new Revit session for a batch job.

tip

For your first software integration, start with one of the intermediate tutorials.

System requirements​

  • A Windows machine with Revit installed. Use Revit 2024 or newer for these examples.
  • Python for the VIKTOR worker. The worker scripts below use only the Python standard library.
  • pyRevit. The Agent Runtime requires version 7.0 or newer.

The worker Python environment is separate from the pyRevit engine that runs the scripts inside Revit.

Two ways to run Revit from VIKTOR​

The worker calls pyRevit to execute your Revit API code:

  • Open model: the Agent Runtime uses the model that is currently open in Revit. Use query to read data, modify to write values back, and dry_run to preview changes and roll them back. Each approved run is one undo entry in Revit.
  • Batch scripts: pyrevit run starts a new Revit session for a model file. Use this approach for batch jobs and file exports, such as the PDF example below.
Experimental feature

The Agent Runtime was introduced in pyRevit 7.0 and is marked as experimental in the pyRevit documentation. Test your scripts locally before deploying them.

Download and install pyRevit​

Download the pyRevit installer. Pick pyRevit_<version>_signed.exe (installs for the current user) or pyRevit_<version>_admin_signed.exe (installs for all users). Follow the installer steps, then check the installation in a new terminal:

pyrevit --version
pyrevit attached

Check that pyRevit is attached to the Revit version you will use. If pyrevit is not found, add the folder that contains pyrevit.exe to your Windows PATH. If pyrevit agent only prints the general help text, check that the command uses pyRevit 7.0 or newer.

Set up the pyRevit Agent Runtime​

Complete this section for the Open model: read and write example. Batch scripts do not need the Agent Runtime.

The Agent Runtime is off by default. To enable it:

  1. Start Revit and open a model.
  2. Go to pyRevit → Settings and expand Agent Runtime (MCP).
  3. Tick Agent Host (Beta). This starts the in-Revit host that the pyrevit agent command connects to.
  4. Choose how Model Changes are handled:
    • Ask for my approval in Revit before every change (default): every modify run shows a Keep/Discard prompt in Revit, with the changed elements selected. Recommended while developing.
    • Auto: changes are applied without a prompt. Each run is still a single undo entry named Agent: <title>.
    • Read-only: only query and dry_run runs are allowed.
  5. Keep IronPython as the Default Script Engine, unless your Revit scripts need CPython packages such as numpy.
  6. Click Save Settings and Reload.

The Connect an Agent buttons register pyRevit with AI coding tools. You do not need to use these buttons or enable the Routes server for this VIKTOR integration.

pyRevit settings: Agent Runtime (MCP)

Check that the open Revit session is listening:

pyrevit agent status
Things to know about the Agent Runtime
  • The pyrevit agent command talks to Revit over a local named pipe that only the same Windows user can open. Run the VIKTOR worker as the same user that runs Revit, e.g. a personal worker in VIKTOR Desktop.
  • Revit must be running with a model open. modify runs are refused when no document is active.
  • With the Ask policy, a modify run waits until the user answers the prompt in Revit. Set the timeouts in the worker script and analysis.execute() long enough for the user to respond.
  • The query mode rejects and rolls back model changes, but the script itself has full access to the worker machine. pyRevit also stores the script and result of each run as plain text under %APPDATA%\pyRevit\agent for 14 days.

Additional information​

Set up the work environment​

Step 1: Add integration to app config​

To make the worker integration available through the interface, add the following to your viktor.config.toml:

worker_integrations = [
"revit",
]

Instructions on how to install the worker:

Install the worker

Follow these steps to install the worker:

A personal worker connects the software that is installed on your own machine, for your own use. Personal workers are installed and managed with VIKTOR Desktop:

  1. Download and install VIKTOR Desktop and log in with your VIKTOR account, if you haven't done so already

  2. In VIKTOR Desktop, click "Add" and select Revit

  3. Select the Python environment of your choice by setting the path to its python.exe as the executable path. This can be your system Python, or the python.exe in a dedicated virtual environment

    tip

    Your default Python environment can usually be found in

    C:\Users\Username\AppData\Local\Programs\Python\Python31X\python.exe

    If you cannot find it, try running the following command in your terminal

    where python
  4. Start the worker. You can view its logs inside VIKTOR Desktop, and the editor shows when your worker is online

note

Personal workers that were installed with the previous per-worker installer keep running, but new personal workers can only be added through VIKTOR Desktop: the per-worker installer download and connection-key flow are no longer available for personal use.

Step 2: Start a VIKTOR app​

Create, install, and start an empty app. Follow the starter guide if you need help.

Step 3: Add the scripts to your VIKTOR app​

The integration uses three types of file:

  • a Revit script that contains the Revit API code and runs inside Revit,
  • a worker script that calls the pyRevit CLI and collects the output,
  • the VIKTOR app (app.py), which sends both scripts to the worker with vkt.revit.RevitAnalysis and reads the result.

Select an approach below. The title of each code block shows the file to create.

Read the open model​

Read data from the model that is currently open in Revit. Query runs reject and roll back model changes.

Revit script. pyRevit provides doc, uidoc, DB (Autodesk.Revit.DB), UI, and inputs. Assign the data you want to return to result; it is converted to JSON (an ElementId becomes an integer).

revit_scripts/walls_by_level.py
walls = DB.FilteredElementCollector(doc).OfClass(DB.Wall).WhereElementIsNotElementType()

rows = []
for wall in walls:
level = doc.GetElement(wall.LevelId)
length = wall.get_Parameter(DB.BuiltInParameter.CURVE_ELEM_LENGTH)
rows.append({
"id": wall.Id,
"type": wall.Name,
"level": level.Name if level else "",
"length_m": DB.UnitUtils.ConvertFromInternalUnits(length.AsDouble(), DB.UnitTypeId.Meters) if length else 0.0,
})

result = {"model": doc.Title, "walls": rows}

Worker script. Reads job.json, runs the Revit script with pyrevit agent run and saves the JSON response that the CLI prints. The response contains status ("ok" on success), result, output (printed text) and error.

revit_scripts/run_agent.py
import json
import os
import shutil
import subprocess
from pathlib import Path

root = Path(__file__).resolve().parent
job = json.loads((root / "job.json").read_text(encoding="utf-8"))

mode = job.get("mode", "query")
if mode not in ("query", "dry_run", "modify"):
raise ValueError("Invalid mode: " + mode)

pyrevit = shutil.which("pyrevit") or os.path.expandvars(r"%APPDATA%\pyRevit-Master\bin\pyrevit.exe")
command = [
pyrevit, "agent", "run", str(root / "revit_task.py"),
"--mode=" + mode,
"--title=" + job.get("title", "VIKTOR"),
"--inputs=" + json.dumps(job.get("inputs", {})),
"--timeout=" + str(job.get("timeout", 240)),
]
if job.get("revit"):
command.append("--revit=" + str(job["revit"])) # Revit year, when several versions are running

process = subprocess.run(command, capture_output=True, text=True, timeout=job.get("timeout", 240) + 30)
try:
response = json.loads(process.stdout[process.stdout.find("{"):])
except ValueError:
response = {"status": "error", "error": (process.stderr or process.stdout).strip()}

(root / "output.json").write_text(json.dumps(response, indent=2), encoding="utf-8")

VIKTOR app.

app.py
import json
from io import BytesIO
from pathlib import Path

import viktor as vkt

SCRIPTS = Path(__file__).parent / "revit_scripts"


def run_agent(task_script, mode="query", inputs=None, title="VIKTOR", timeout=240):
job = {"mode": mode, "title": title, "inputs": inputs or {}, "timeout": timeout}
analysis = vkt.revit.RevitAnalysis(
script=vkt.File.from_path(SCRIPTS / "run_agent.py"),
files=[
("revit_task.py", vkt.File.from_path(SCRIPTS / task_script)),
("job.json", BytesIO(json.dumps(job).encode())),
],
output_filenames=["output.json"],
)
analysis.execute(timeout=timeout + 60)
response = json.loads(analysis.get_output_file("output.json").getvalue())
if response.get("status") != "ok":
error = response.get("error")
message = error.get("message") if isinstance(error, dict) else error
raise vkt.UserError(f"Revit run failed: {message}")
return response


class Parametrization(vkt.Parametrization):
intro = vkt.Text("Shows the walls of the model that is currently open in Revit.")


class Controller(vkt.Controller):
parametrization = Parametrization

@vkt.TableView("Walls in the open Revit model", duration_guess=10)
def walls(self, params, **kwargs):
result = run_agent("walls_by_level.py", mode="query")["result"]
rows = [[w["id"], w["type"], w["level"], round(w["length_m"], 2)] for w in result["walls"]]
return vkt.TableResult(rows, column_headers=["Id", "Type", "Level", "Length [m]"])

Write to the open model​

Write values back to the model that is currently open in Revit. The worker script run_agent.py and the run_agent() helper are the same as in the read example above.

Revit script. In modify mode the script opens its own transaction. Values from VIKTOR arrive in inputs.

revit_scripts/set_wall_comments.py
level_name = inputs["level"]
comment = inputs["comment"]

walls = DB.FilteredElementCollector(doc).OfClass(DB.Wall).WhereElementIsNotElementType()

t = DB.Transaction(doc, "Set wall comments")
t.Start()
updated = []
for wall in walls:
level = doc.GetElement(wall.LevelId)
if level is None or level.Name != level_name:
continue
parameter = wall.get_Parameter(DB.BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS)
if parameter and not parameter.IsReadOnly:
parameter.Set(comment)
updated.append(wall.Id)
t.Commit()

result = {"updated": len(updated), "ids": updated}

VIKTOR app. Keep the imports, SCRIPTS, and run_agent() helper from the read example above. Replace Parametrization and Controller with the classes below. Use an ActionButton, so the model is only changed when the user clicks the button. Select "Preview only" to use --mode=dry_run, which reports the changes and rolls them back.

app.py
class Parametrization(vkt.Parametrization):
intro = vkt.Text(
"Writes a comment to all walls on a level of the model that is open in Revit. "
"Depending on the pyRevit policy, you may need to approve the change in Revit."
)
level = vkt.TextField("Level name", default="Level 1")
comment = vkt.TextField("Comment", default="Checked in VIKTOR")
preview = vkt.BooleanField("Preview only (dry run)", default=True)
apply = vkt.ActionButton("Write to Revit", method="write_comments", longpoll=True)


class Controller(vkt.Controller):
parametrization = Parametrization

def write_comments(self, params, **kwargs):
response = run_agent(
"set_wall_comments.py",
mode="dry_run" if params.preview else "modify",
inputs={"level": params.level, "comment": params.comment},
title="VIKTOR wall comments",
timeout=600, # leave time to approve the change in Revit
)
vkt.UserMessage.info(
f"Walls processed: {response['result']['updated']} (decision: {response.get('decision')})"
)

Test the scripts locally​

Test your scripts on your computer before you set up a worker on another machine. Run a Revit script from a Windows Command Prompt with the same command the worker uses:

pyrevit agent run revit_scripts\walls_by_level.py --mode=query
pyrevit agent run revit_scripts\set_wall_comments.py --mode=dry_run --inputs="{\"level\": \"Level 1\", \"comment\": \"Test\"}"
pyrevit agent runs

pyrevit agent runs lists the recent runs, and pyrevit agent show <run_id> prints the script, request and response of a run.

The Revit API documentation uses C#, but the API names are the same in pyRevit. Refer to the Revit API documentation for classes and methods. Revit stores lengths in feet; convert them with DB.UnitUtils, as shown in the query example.

Example app​

Try the pyRevit integration app. It reads element counts and applies colours by category to a view in the open Revit model. Its docs/images folder also shows how to configure pyRevit and the Revit worker in VIKTOR Desktop.

Testing​

RevitAnalysis.execute needs to be mocked within the context of (automated) testing.

The viktor.testing module provides the mock_RevitAnalysis decorator to mock workers:

import unittest

import viktor as vkt

from viktor.testing import mock_RevitAnalysis

from app.my_entity_type.controller import MyEntityTypeController


class TestMyEntityTypeController(unittest.TestCase):
@mock_RevitAnalysis(get_output_file={
'result.xml': vkt.File.from_path('test_file.xml'), # <name>: <File>
'result.json': vkt.File.from_path('test_file.json'),
})
def test_analysis(self):
MyEntityTypeController().analysis()

The decorator accepts these input values:

  • A sequence: each method call returns the next item. A call after the last item raises an exception.
  • A single object: each method call returns the same object.
  • None (default): each method call returns a default File or BytesIO object with empty content.