Start

Your first button

Build a working Navisworks tool from an empty folder in about five minutes, then extend it with an icon, a tooltip, a keyboard shortcut and a settings dialog.

This page builds one real tool end to end. Every later page goes deeper on one part of what happens here, so it is worth typing along rather than skimming.

The tool counts what is selected in the model and reports it. Small, but it exercises the whole path: discovery, metadata, the injected globals, the pynavis library and the toast surface.

Before you start#

You need Navisworks with pyNavis installed, and a folder that pyNavis scans for extensions. Check which folders those are: open %APPDATA%\pyNavis\config.json and look at the "extensions" array.

%APPDATA%\pyNavis\config.json
{
  "extensions": [
    "D:\\NavisTools"
  ]
}

If the file does not exist or has no "extensions" key, pyNavis is reading from its default root, %APPDATA%\pyNavis\extensions, where the shipped extension sits. You can either put your extension there or create the config file above. A folder you own is easier to work in, so this guide assumes D:\NavisTools.

Shortcut

The command line does this for you: pynavis extensions add D:\NavisTools. Run pynavis extensions list to confirm what is registered.

Step 1: create the folders#

Four nested folders, and the suffixes are what matter. The names before the suffixes are yours to choose and become the labels you see.

D:\NavisTools
D:\NavisTools\
  MyTools.extension\
    MyTools.tab\
      Selection.panel\
        Count.pushbutton\
          script.py

Create them in Explorer or in one line:

powershell
New-Item -ItemType Directory -Force `
  D:\NavisTools\MyTools.extension\MyTools.tab\Selection.panel\Count.pushbutton
Watch the suffixes

.extension, .tab, .panel and .pushbutton are matched literally, though case does not matter. A folder named Count.pushButton works. Count.pushbuttons or Count.button builds nothing; because it sits inside a panel, the runtime does at least say so, in the log and in a toast. A misspelled .extension, .tab or .panel is the genuinely silent one.

Step 2: write the script#

Create script.py inside Count.pushbutton:

Count.pushbutton\script.py
"""Counts the items in the current selection."""
from pynavis import selection, toast

items = selection.get_items()

if items:
    toast.success('%d item(s) selected' % len(items))
else:
    toast.info('Nothing selected', 'Pick something in the model and run this again.')

Three things are already happening that you did not configure:

  • The button label is Count, taken from the folder name.
  • The tooltip is Counts the items in the current selection., taken from the module docstring on the first line.
  • The script runs on IronPython, the default engine.

Step 3: load it#

Start Navisworks, or if it is already open, click Reload on the pyNavis panel. A new MyTools tab appears with a Selection panel and a Count button on it.

Select a few objects and click the button. A toast appears in the corner of the Navisworks window with the count.

Nothing appeared

Watch for a warning toast reading N bundle folders were skipped: pyNavis shows one after every scan that had to drop something, and quotes the first problem in full. The rest are in %APPDATA%\pyNavis\logs\pyNavis.log. The most common causes are a typo in a bundle suffix, a missing script.py, or an extension root that is not in config.json. The last of those is the one that produces no toast and no log line at all, because the scan never reaches the folder. Troubleshooting lists every failure mode with its log line.

Step 4: add metadata#

The folder name is a poor title once you want spaces and punctuation, and the docstring is a poor tooltip once you want more than a sentence. Add a bundle.yaml beside the script:

Count.pushbutton\bundle.yaml
title: Count selection
tooltip: Reports how many objects are currently selected.
shortcut: Ctrl+Shift+K

Click Reload. The button is now labelled Count selection, the tooltip reads better, and pressing Ctrl+Shift+K anywhere in the Navisworks main window runs it without touching the ribbon.

Metadata resolves in a fixed order, most specific first:

bundle.yaml title: / tooltip: if absent script.py __title__ / docstring if absent Count.pushbutton folder name, prefix stripped Tooltips have no third fallback: without a yaml tooltip or a docstring, the button simply has none.

Step 5: add an icon#

Without an icon the button renders as text only. Drop a 96×96 PNG named icon.png into the bundle folder and Reload.

folders
Count.pushbutton\
  script.py
  bundle.yaml
  icon.png            # 96x96, large buttons
  icon.dark.png       # 96x96, dark theme
  icon.small.png      # 32x32, stacks and pulldown menus
  icon.small.dark.png # 32x32, dark theme

Only icon.png is required; the other three fall back to it. Supplying all four is what makes a tool look native in both themes and at both sizes. Icons covers the drawing rules and the generator that produces the shipped set.

Step 6: add a settings dialog#

A bundle can ship a second script called config.py. Shift-clicking the button runs it instead of script.py, which is where a tool's options live.

Count.pushbutton\config.py
"""Options for Count selection."""
from pynavis import forms, settings, toast

TOOL = 'count_selection'
DEFAULTS = {'warn_above': 1000}

values = settings.load(TOOL, DEFAULTS)
answer = forms.ask_string(
    'Warn when the selection is larger than:',
    default=str(values['warn_above']),
    title='Count selection',
)

if answer is not None:                      # None means cancelled: change nothing
    try:
        values['warn_above'] = int(answer)
    except ValueError:
        toast.error('That is not a whole number')
    else:
        settings.save(TOOL, values)
        toast.success('Saved', 'Warning above %d items.' % values['warn_above'])

Now read it back in the main script:

Count.pushbutton\script.py
"""Counts the items in the current selection."""
from pynavis import selection, settings, toast

TOOL = 'count_selection'
DEFAULTS = {'warn_above': 1000}

values = settings.load(TOOL, DEFAULTS)
items = selection.get_items()

if not items:
    toast.info('Nothing selected', 'Pick something in the model and run this again.')
elif len(items) > values['warn_above']:
    toast.warning('%d item(s) selected' % len(items), 'That is a large selection.')
else:
    toast.success('%d item(s) selected' % len(items))

settings.load never raises. A missing, empty or corrupt settings file yields a fresh copy of your defaults, and keys you have since removed from DEFAULTS are dropped rather than leaking back in.

The three click actions#

Your bundle now responds to all three, and you only wrote code for two of them:

ActionRunsNotes
Clickscript.pyThe primary action.
Shift+Clickconfig.py Same engine, same globals. Without a config.py, an informational toast says the action is not defined.
Alt+ClickNothing Opens the bundle folder in Explorer with script.py selected. Built in, and the fastest way back to your own code.
Keyboard shortcuts always run the primary script

Ctrl+Shift+K runs script.py, not config.py, even though the chord contains Shift. Modifier click actions and keyboard chords are separate mechanisms.

What you have built#

folders
D:\NavisTools\
  MyTools.extension\
    MyTools.tab\
      Selection.panel\
        Count.pushbutton\
          script.py            # the primary action
          config.py            # Shift+Click, the settings dialog
          bundle.yaml          # title, tooltip, shortcut
          icon.png             # 96x96

That is a complete pyNavis tool. Everything else in these docs is a richer way to present it on the ribbon, a script that runs itself when something happens in the model, or a larger slice of the Navisworks API to point it at.