Views > Navigate

Coordinate Spotter

Click points in the view to drop numbered markers with their coordinates, then get the table, a clipboard copy and an Excel or CSV file. The clicks use the Measure tool, so the pointer snaps to vertices and edges exactly as a measurement does; Esc or right-click ends. Shift+Click sets a local origin.

Tab and panelViews > Navigate
KindButton
Live whenA model is open

Status

Launched Ran inside Navisworks at least once with no error logged; nobody checked the result yet.

Recorded on 2026-10-05. The evidence is a line in a pyNavis log.

Related asks to confirm

No quote is on record yet. These threads look related, but a person still has to open and read them.

Installs with

Coordinate Spotter sits in a stack, so installing it also installs:

Events it listens to

None. It runs only when you click it.

Files

Paths are relative to the Playground.extension folder.

  • Views.tab/Navigate.panel/Spotter.stack/Coordinate_Spotter.pushbutton/bundle.yaml
  • Views.tab/Navigate.panel/Spotter.stack/Coordinate_Spotter.pushbutton/config.py
  • Views.tab/Navigate.panel/Spotter.stack/Coordinate_Spotter.pushbutton/icon.dark.png
  • Views.tab/Navigate.panel/Spotter.stack/Coordinate_Spotter.pushbutton/icon.png
  • Views.tab/Navigate.panel/Spotter.stack/Coordinate_Spotter.pushbutton/icon.small.dark.png
  • Views.tab/Navigate.panel/Spotter.stack/Coordinate_Spotter.pushbutton/icon.small.png
  • Views.tab/Navigate.panel/Spotter.stack/Coordinate_Spotter.pushbutton/script.py

Install

  1. Only this tool. In Navisworks, click pyNavis > Playground, search for Coordinate Spotter, tick it and click Install.
  2. The whole extension. Download Playground.extension.zip, unzip it, put the Playground.extension folder in %APPDATA%\pyNavis\extensions\, and click pyNavis > Reload. Coordinate Spotter is on the Views tab, Navigate panel.

You need pyNavis 1.3.0 or later.

Tried it? Tell us whether it worked or not. That report is what moves a tool up the status ladder.

Report

Source

The tool's own files, word for word as published in pynavis/Playground. Paths are relative to the Playground.extension folder.

script.py

The script that runs on a click.

Views.tab/Navigate.panel/Spotter.stack/Coordinate_Spotter.pushbutton/script.py View on GitHub
"""Drops numbered coordinate markers where you click until Esc, then lists the points and copies them."""
import os

from namo import levels, picking, spotter, vp_common
from pynavis import forms, output, overlay, script, toast

TITLE = 'Coordinate Spotter'
SAVE = ['Save as Excel', 'Save as CSV']


def _overlay_redraw():
    """Redraws the overlay through namo.overlayfix, so labels land on their points."""
    from namo import overlayfix
    return overlayfix.redraw()


def pick_loop(document, units, origin, holder, size):
    """Picks until Esc, a right-click or another tool; returns how many points were
    added. Each pick runs on the Measure tool with its prompt on the banner
    (namo.picking shows and clears it), and the prompt carries the last reading."""
    added = 0
    while True:
        n = holder.count + 1
        last = holder.points[-1] if holder.points else None
        hit = picking.point(spotter.prompt_text(n, last, units, origin), want_item=True, doc=document)
        if hit is None:
            return added
        record = spotter.record_of(n, hit)
        holder.points.append(record)
        holder.count = n
        added += 1
        spotter.draw(record, units, origin, size)
        _overlay_redraw()


def save(document, head, rows, origin, units):
    """Offers Excel or CSV; True when saved, False when it failed, None when skipped."""
    choice = forms.ask_options(None, SAVE, title=TITLE)
    if choice is None:
        return None
    excel = choice == SAVE[0]
    stem = '%s - Coordinates' % vp_common.file_stem(vp_common.doc_path(document))
    folder = vp_common.default_folder(vp_common.doc_path(document))
    if excel:
        path = forms.save_file(filter='Excel workbooks (*.xlsx)|*.xlsx',
                               default_name=os.path.join(folder, stem + '.xlsx'), title=TITLE)
    else:
        path = forms.save_file(filter='CSV files (*.csv)|*.csv',
                               default_name=os.path.join(folder, stem + '.csv'), title=TITLE)
    if path is None:
        return None
    title = 'Coordinates in %s' % units.lower()
    if origin:
        title += ', measured from the local origin at %s' % vp_common.point_text(origin, units)
    try:
        if excel:
            path = path if path.lower().endswith('.xlsx') else path + '.xlsx'
            spotter.write_xlsx(path, head, rows, title)
        else:
            path = path if path.lower().endswith('.csv') else path + '.csv'
            spotter.write_csv(path, head, rows)
    except (IOError, OSError) as error:
        toast.error('Could not save the points', '%s. Close the file, then try again.' % error)
        return False
    toast.success('Saved %s' % vp_common.plural(len(rows), 'point'), path)
    return True


def run():
    document = vp_common.active_doc()
    if not vp_common.has_models(document):
        toast.info('No model open', 'Open or append a model, then run Coordinate Spotter again.')
        return
    units = str(document.Units)
    origin = spotter.load_origin(document)
    holder = spotter.state()
    key = vp_common.doc_path(document).lower() or 'untitled'
    if holder.doc is not None and holder.doc != key:
        spotter.clear()
        holder = spotter.state()
    holder.doc = key
    size = spotter.marker_size(units, levels.model_box(document))
    if not pick_loop(document, units, origin, holder, size):
        return                                  # ended without a new point: say nothing
    head = spotter.headers(units, origin)
    where = ''
    if origin:
        where = ', measured from the local origin at %s' % vp_common.point_text(origin, units)
    output.set_title(TITLE)
    output.print_md('## Spotted points\nIn %s%s. Clear Spots removes the markers.' % (units.lower(), where))
    output.print_table(spotter.display_rows(holder.points, units, origin), head)
    rows = spotter.data_rows(holder.points, units, origin)
    script.clipboard_copy(spotter.tsv(head, rows))
    if save(document, head, rows, origin, units) is None:
        toast.success('%s spotted' % vp_common.plural(len(holder.points), 'point'),
                      'The table is in the output window and on the clipboard.')


run()

config.py

The Shift+Click action.

Views.tab/Navigate.panel/Spotter.stack/Coordinate_Spotter.pushbutton/config.py View on GitHub
"""Sets the local origin Coordinate Spotter measures from, or goes back to the model origin."""
from namo import levels, picking, spotter, vp_common
from pynavis import forms, toast

TITLE = 'Coordinate Spotter origin'
CHOICES = ['Pick the local origin', 'Type the local origin', 'Use the model origin']


def run():
    document = vp_common.active_doc()
    if not vp_common.has_models(document):
        toast.info('No model open', 'Open or append a model first.')
        return
    units = str(document.Units)
    current = spotter.load_origin(document)
    where = vp_common.point_text(current, units) if current else 'the model origin'
    choice = forms.ask_options('Coordinates are measured from %s.' % where, CHOICES, title=TITLE)
    if choice is None:
        return
    origin = None
    if choice == CHOICES[0]:
        hit = picking.point('Click the point to measure from')
        if hit is None:                         # Esc, right-click or another tool: say nothing
            return
        origin = hit.point
    elif choice == CHOICES[1]:
        start = ', '.join('%.3f' % v for v in current) if current else '0, 0, 0'
        text = forms.ask_string('Local origin X, Y, Z in %s, with commas between:' % units.lower(),
                                default=start, title=TITLE)
        if text is None:
            return
        origin = spotter.parse_xyz(text, units)
        if origin is None:
            toast.error('That is not three coordinates', 'Type X, Y and Z with commas between, like 12.5, 3, 0.')
            return
    try:
        spotter.save_origin(document, origin)
    except (IOError, OSError) as error:
        toast.error('Could not save the origin', str(error))
        return
    if spotter.state().points:
        spotter.redraw_all(units, origin, spotter.marker_size(units, levels.model_box(document)))
    if origin is None:
        toast.success('Measuring from the model origin')
    else:
        toast.success('Local origin set', vp_common.point_text(origin, units))


run()

Library modules this tool uses

From the shared namo library, which installs with every tool. Each name opens the module on GitHub.

  • namo.levelsLevels of the model, and sectioning the view at one of them.
  • namo.overlayfixKeeps pynavis.overlay labels on their points, whichever way up the view counts pixels.
  • namo.pickingThe one way a Playground tool asks for a click in the 3D view.
  • namo.spotterCoordinate Spotter: numbered coordinate markers dropped by clicking, a table of them, and Clear Spots.
  • namo.vp_commonShared helpers for the Views tools (Viewpoints, Capture and Navigate panels).

bundle.yaml

The bundle contract: the keys pyNavis reads for this tool.

Views.tab/Navigate.panel/Spotter.stack/Coordinate_Spotter.pushbutton/bundle.yaml View on GitHub
title: Coordinate Spotter
tooltip: Click points in the view to drop numbered markers with their coordinates, then get the table, a clipboard copy and an Excel or CSV file. The clicks use the Measure tool, so the pointer snaps to vertices and edges exactly as a measurement does; Esc or right-click ends. Shift+Click sets a local origin.
context: doc