Start
How pyNavis works
The mental model behind every tool you will write: folders on disk become buttons on the ribbon, and a button runs a Python file.
pyNavis is a Python scripting host for Autodesk Navisworks. It loads a plugin into Navisworks at startup, scans folders on disk, and builds a ribbon tab from what it finds. Clicking a button executes a Python file. That is the heart of the system, and everything else, from event hooks to dock panels, hangs off the same folder scan.
If you have written pyRevit extensions, this will feel familiar and mostly transfers.
If you have not, the important idea is that the folder structure is the
configuration. There is no manifest listing your commands, no registration call, no
project file, and no compile step. A folder named Export.pushbutton containing a
file named script.py is a working button.
From disk to ribbon#
Each level of the folder tree maps to one level of the ribbon. The suffix on the folder name decides what it becomes, and the folder name itself becomes the visible label.
What a button actually does#
When you click a button, the runtime picks an engine, creates a fresh scope, injects four
variables into it, and executes script.py top to bottom. Your script talks to
Navisworks through the pynavis library, which wraps the .NET API in something
Pythonic. The library is organised by domain: selection, sets,
props, viewpoints, clash, clashtest and
export face the model, toast, forms and
output face the user, and the API reference
lists every function in all of them.
"""Exports the current selection to CSV."""
from pynavis import forms, selection, toast
items = selection.get_items()
if not items:
toast.error('Nothing selected', 'Select something first, then run this again.')
else:
path = forms.save_file(default_name='points.csv')
if path: # None means the user cancelled
with open(path, 'w') as handle:
for item in items:
handle.write(item.DisplayName + '\n')
toast.success('Exported %d items' % len(items), path)That script is a complete, shippable tool. It has an icon-less button, a tooltip taken from its docstring, a title taken from its folder name, and it reports its result without opening a window.
The two engines#
Every bundle runs on one of two Python engines. The choice is a single line in
bundle.yaml, and it matters more than it looks.
| IronPython | CPython | |
|---|---|---|
| Engine id | ironpython (the default) | cpython |
| Version | 3.4 | Whatever is installed, typically 3.11 or later |
| Runs in-process | Yes, on the Navisworks .NET runtime | Yes, through pythonnet |
| Can subclass .NET and COM types | Yes | No |
| Third-party wheels (numpy, pandas) | No | Yes |
| Needs Python installed | No, it ships with pyNavis | Yes |
| Use it when | Almost always | You need a compiled package or modern syntax |
Start on IronPython. It is the default, it has no external dependency, and it is the only
engine that can subclass the COM interfaces some parts of the Navisworks API require. Move a
single bundle to CPython only when you hit a wall, such as needing numpy.
Beyond the plain button#
The pushbutton is one of twelve folder kinds a panel understands. Stacks and pulldowns
pack more tools into the same ribbon space; toggles, smartbuttons and the two split buttons
change how a button behaves; nobuttons, urlbuttons and linkbuttons cover the commands that
are not really scripts. Two are not buttons at all: *.dockpane is a dockable
Navisworks panel whose content is WPF from a pane.xaml, shown and hidden by a
ribbon toggle, and *.slideout is a marker folder whose contents move into the
flyout the panel title opens. Buttons, stacks and pulldowns walks
through all twelve.
Code can also run without a click. An extension may carry
hooks\<event>.py scripts that run themselves when one of fourteen
Navisworks events fires, from doc-opened to camera-moved, and a
startup.py at its root that runs at boot and on every Reload, before that
extension's ribbon builds. Hooks covers the events and their rules;
Anatomy of an extension covers startup.py and the
shared *.lib library folders.
Where everything lives#
Everything pyNavis installs lands in your own user profile, which is why installing it
needs no administrator rights. Two installations are possible and they differ only in where
the runtime and your extensions sit. An ordinary install is what the setup program and
tools/install.ps1 produce; a development install, from
tools/deploy-dev.ps1, points the runtime back at a repository checkout.
| What | Installed | Development |
|---|---|---|
| Loader plugin | %APPDATA%\Autodesk\ApplicationPlugins\pyNavis.bundle\Contents\<year>\ | |
| Extensions | %APPDATA%\pyNavis\extensions |
Any folder listed in config.json |
| Runtime and stdlib | %APPDATA%\pyNavis\<year>\runtime |
bin\<year>\runtime in the checkout |
pynavis library |
Beside the runtime | pynavislib\ in the checkout |
| Your settings | %APPDATA%\pyNavis\config.json | |
| Per-tool settings | %APPDATA%\pyNavis\settings\<tool>.json | |
| Selection memory | %APPDATA%\pyNavis\memory\ | |
| Logs | %APPDATA%\pyNavis\logs\ | |
The runtime scans every folder listed under "extensions" in
config.json, plus two defaults: %APPDATA%\pyNavis\extensions, where
the shipped extension lives, and %PROGRAMDATA%\pyNavis\extensions if that folder
exists, which is how an IT department deploys extensions to a whole machine. All of them are
scanned, so a development extension and an installed one can coexist.
The bundle folder holds one Contents\<year> folder per Navisworks
release and a PackageContents.xml naming them, so a single install serves every
release on the machine and Navisworks loads only the folder matching its own. The runtime the
loader then hands over to is resolved in order: the PYNAVIS_RUNTIME environment
variable, the
runtime<year> key in config.json,
%APPDATA%\pyNavis\<year>\runtime, then
%PROGRAMDATA%\pyNavis\<year>\runtime. The first one that exists wins, and
the log names the winner near the top.
The development loop#
The loop is short, which is the point of the whole system.
- Create or edit a file inside your extension folder.
- Click Reload on the pyNavis panel.
- Click your button.
Reload rescans every extension folder and rebuilds the ribbon without restarting
Navisworks. It picks up new bundles, renamed folders, changed bundle.yaml
values, new icons, and changed shortcuts. It also reruns each extension's
startup.py, re-reads the hook scripts, and rebuilds the content of any dock
panel that is open.
Edits to script.py and to modules inside your bundle or your extension's
lib\ folder are picked up on every run, with no Reload needed, because
the runtime drops those modules from the cache before each execution. Edits to the
pynavis library itself, or to modules inside a shared *.lib
folder, need a Reload. Changes to the C# runtime, adding or removing a whole
*.lib folder, and new panel slots need a Navisworks restart.
What you cannot do#
Being honest about the boundaries saves time:
- No background threads touching the model. The Navisworks API is not thread-safe. Your script runs on the UI thread and should stay there.
- One script at a time. The runtime refuses a second run while one is in flight and toasts “still running”. Long jobs block the UI, so report progress.
- No custom ribbon controls. The ribbon vocabulary is the twelve bundle
kinds. There are no combo boxes, sliders or galleries on the ribbon itself; when a tool
needs persistent controls, give it a
*.dockpanepanel instead. - No package installer. There is no
requirements.txthandling. Vendor pure-Python dependencies into your extension'slib\folder, or use CPython and its own site-packages.