Views > Viewpoints

Batch Viewpoints

Saves a viewpoint for every level, grid intersection, selection or search set, or model in one go: plans cut at each level, eye-level views or zoomed sets, named and filed by template. One Ctrl+Z removes the batch.

Tab and panelViews > Viewpoints
KindButton
Live whenA model is open

Status

Untested Never executed inside Navisworks. Passed the headless checks only.

The public ask

No public source is on record for this tool.

Events it listens to

None. It runs only when you click it.

Files

Paths are relative to the Playground.extension folder.

  • Views.tab/Viewpoints.panel/Batch_Viewpoints.pushbutton/bundle.yaml
  • Views.tab/Viewpoints.panel/Batch_Viewpoints.pushbutton/icon.dark.png
  • Views.tab/Viewpoints.panel/Batch_Viewpoints.pushbutton/icon.png
  • Views.tab/Viewpoints.panel/Batch_Viewpoints.pushbutton/icon.small.dark.png
  • Views.tab/Viewpoints.panel/Batch_Viewpoints.pushbutton/icon.small.png
  • Views.tab/Viewpoints.panel/Batch_Viewpoints.pushbutton/script.py

Install

  1. Only this tool. In Navisworks, click pyNavis > Playground, search for Batch Viewpoints, 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. Batch Viewpoints is on the Views tab, Viewpoints 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/Viewpoints.panel/Batch_Viewpoints.pushbutton/script.py View on GitHub
"""Generates saved viewpoints per level, per grid intersection, per selection or search set, or per model, in one undo step."""
import os

from namo import items, levels, progress, ui, vp_common, vpbatch
from pynavis import forms, script, toast

TITLE = 'Batch Viewpoints'
INTROS = {
    'level': 'One viewpoint per level. A plan looks straight down with the level cut at the '
             'height below; an eye-level view stands in from the south-west corner of the floor.',
    'grid': 'One viewpoint per grid intersection on the level you choose.',
    'set': 'One viewpoint per set, zoomed to its items. Each view looks the way the current view looks.',
    'model': 'One viewpoint per model, zoomed to it. Each view looks the way the current view looks.',
}


def load_levels(document):
    """Levels (estimated ones read with a progress window), or None when cancelled."""
    found = levels.cached_or_none(document)
    if found is not None:
        return found
    outcome = progress.run(TITLE, lambda report: levels.find(document, report), 'Looking for levels')
    return None if outcome is progress.CANCELLED else outcome


def choose_levels(level_list, units):
    rows = [{'value': i, 'cells': [lv['name'], vp_common.length_text(lv['elevation'], units),
                                   'Estimated' if lv.get('approximate') else 'Grid']}
            for i, lv in enumerate(level_list)]
    picked = ui.choose('%s: levels' % TITLE, rows, ['Level', 'Elevation', 'Source'],
                       checked=list(range(len(level_list))), ok='Save viewpoints')
    return None if picked is None else [level_list[i] for i in picked]


def level_run(document, cfg, level_list):
    chosen = choose_levels(level_list, str(document.Units))
    if chosen is None:
        return None
    return vpbatch.level_jobs(document, level_list, chosen, cfg)


def grid_run(document, cfg, level_list, values):
    lv = None
    for candidate in level_list:
        if candidate['name'] == values.get('grid_level'):
            lv = candidate
    grid_level = vpbatch.grid_level_object(document, values.get('grid_level'))
    box = levels.model_box(document)
    if lv is None or grid_level is None or box is None:
        toast.error('Could not read that grid level', 'Check the grid system in Grids and Levels.')
        return None
    k = 1.0 / vp_common.to_meters_factor(str(document.Units))
    outcome = progress.run(TITLE, lambda report: vpbatch.find_intersections(
        document, grid_level, box, cfg['grid_spacing_m'] * k, report), 'Finding grid intersections')
    if outcome is progress.CANCELLED:
        return None
    found, _step = outcome
    if not found:
        toast.info('No grid intersections found', 'The grid lines on %s do not cross inside the model.' % lv['name'])
        return None
    rows = [{'value': i, 'cells': [r['name'], r['line1'], r['line2']]} for i, r in enumerate(found)]
    picked = ui.choose('%s: intersections on %s' % (TITLE, lv['name']), rows, ['Intersection', 'Line', 'Line'],
                       checked=list(range(len(found))), ok='Save viewpoints')
    if picked is None:
        return None
    return vpbatch.grid_jobs(document, level_list, lv, [found[i] for i in picked], cfg), False


def set_run(document, cfg, entries):
    rows = [{'value': i, 'cells': [vp_common.folder_label(e['folder']), e['name']]} for i, e in enumerate(entries)]
    picked = ui.choose('%s: sets' % TITLE, rows, ['Folder', 'Set'], checked=list(range(len(entries))),
                       ok='Save viewpoints')
    if picked is None:
        return None

    def resolve(report):
        targets = []
        for n, i in enumerate(picked):
            if vp_common.report_step(report, float(n) / len(picked), 'Reading %s' % entries[i]['name']):
                raise items.Cancelled()
            found = vpbatch.set_items(document, entries[i])
            if found:
                targets.append({'name': entries[i]['name'], 'folder': entries[i]['folder'], 'items': found})
        return targets
    targets = progress.run(TITLE, resolve, 'Reading the sets')
    if targets is progress.CANCELLED:
        return None
    if not targets:
        toast.info('The chosen sets are empty', 'Nothing in the model matches them.')
        return None
    return vpbatch.zoom_jobs(document, targets, 'set', cfg), False


def model_run(document, cfg):
    models = items.models(document)
    rows = [{'value': m['index'], 'cells': [m['name'], m['file']]} for m in models]
    picked = ui.choose('%s: models' % TITLE, rows, ['Model', 'File'], checked=[m['index'] for m in models],
                       ok='Save viewpoints')
    if picked is None:
        return None
    targets = [{'name': os.path.splitext(m['name'])[0] or m['name'], 'items': [m['root']]}
               for m in models if m['index'] in picked]
    return vpbatch.zoom_jobs(document, targets, 'model', cfg), False


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 Batch Viewpoints again.')
        return
    cfg = vpbatch.settings(script.get_config(vpbatch.DEFAULTS))
    labels = [label for _g, label in vpbatch.GENERATORS]
    picked = forms.ask_options(None, labels, title=TITLE)
    if picked is None:
        return
    generator = vpbatch.GENERATORS[labels.index(picked)][0]
    units = str(document.Units)
    level_list = []
    entries = []
    level_options = None
    if generator in ('level', 'grid'):
        level_list = load_levels(document)
        if level_list is None:
            return
        if not level_list:
            toast.info('No levels found', 'Add a grid system with levels (Grids and Levels), or open a model '
                                          'whose objects carry a Level property.')
            return
        if generator == 'grid':
            level_list = [lv for lv in level_list if lv.get('source') == 'grid']
            if not level_list:
                toast.info('No grid system', 'Per grid intersection needs grids and levels. Turn them on in '
                                             'Grids and Levels, then run this again.')
                return
            level_options = [('%s (%s)' % (lv['name'], vp_common.length_text(lv['elevation'], units)), lv['name'])
                             for lv in level_list]
    elif generator == 'set':
        entries = vpbatch.selection_sets(document)
        if not entries:
            toast.info('No selection or search sets', 'Save some sets in the Sets window, then run this again.')
            return
    values = ui.form(TITLE, vpbatch.fields_for(generator, cfg, units, level_options),
                     intro=INTROS[generator], ok='Next', width=500, units=units)
    if values is None:
        return
    cfg = vpbatch.update_settings(cfg, generator, values, units)
    script.save_config(cfg)
    if generator == 'level':
        made = level_run(document, cfg, level_list)
    elif generator == 'grid':
        made = grid_run(document, cfg, level_list, values)
    elif generator == 'set':
        made = set_run(document, cfg, entries)
    else:
        made = model_run(document, cfg)
    if made is None:
        return
    jobs, sampled = made
    if not jobs:
        toast.info('Nothing chosen', 'Tick at least one row, then run Batch Viewpoints again.')
        return
    result = progress.run(TITLE, lambda report: vpbatch.run(document, jobs, cfg, report), 'Saving viewpoints')
    if result is progress.CANCELLED:
        return
    level, message, detail = vpbatch.summary(result)
    if sampled and level == 'success':
        detail += ' Very large model: some level frames were estimated from part of the tree.'
    toast.show(level, message, detail)


run()

Library modules this tool uses

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

  • namo.itemsModel items: walking, collecting, identifying, measuring and showing them.
  • namo.levelsLevels of the model, and sectioning the view at one of them.
  • namo.progressGlue between pyNavis' cancellable progress window and Playground's progress callbacks.
  • namo.uiWPF building blocks for Playground tools, themed from the pyNavis design system.
  • namo.vp_commonShared helpers for the Views tools (Viewpoints, Capture and Navigate panels).
  • namo.vpbatchBatch Viewpoints: saved viewpoints generated per level, per grid intersection, per selection or search set, or per model.

bundle.yaml

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

Views.tab/Viewpoints.panel/Batch_Viewpoints.pushbutton/bundle.yaml View on GitHub
title: Batch\nViewpoints
tooltip: Saves a viewpoint for every level, grid intersection, selection or search set, or model in one go: plans cut at each level, eye-level views or zoomed sets, named and filed by template. One Ctrl+Z removes the batch.
context: doc