Coordinate > Issues

Clash to Issues

Turns each clash group and ungrouped clash of the chosen tests into an issue, once, with its clash view, snapshot and elements, and a trade, priority and due date from rules. Shift+Click for settings.

Tab and panelCoordinate > Issues
KindButton
Live whenThe model has clash results

Status

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

The public ask

When an issue is created, grouped clashes should be locked to ensure Clash–Issue stability.

Autodesk Community. Open the source

The page would not open to an automated fetch, so this is the search engine's own snippet, as shown.

Installs with

Clash to Issues 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.

  • Coordinate.tab/Issues.panel/Issue_Tools.stack/Clash_to_Issues.pushbutton/bundle.yaml
  • Coordinate.tab/Issues.panel/Issue_Tools.stack/Clash_to_Issues.pushbutton/config.py
  • Coordinate.tab/Issues.panel/Issue_Tools.stack/Clash_to_Issues.pushbutton/icon.dark.png
  • Coordinate.tab/Issues.panel/Issue_Tools.stack/Clash_to_Issues.pushbutton/icon.png
  • Coordinate.tab/Issues.panel/Issue_Tools.stack/Clash_to_Issues.pushbutton/icon.small.dark.png
  • Coordinate.tab/Issues.panel/Issue_Tools.stack/Clash_to_Issues.pushbutton/icon.small.png
  • Coordinate.tab/Issues.panel/Issue_Tools.stack/Clash_to_Issues.pushbutton/script.py

Install

  1. Only this tool. In Navisworks, click pyNavis > Playground, search for Clash to Issues, 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. Clash to Issues is on the Coordinate tab, Issues 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.

Coordinate.tab/Issues.panel/Issue_Tools.stack/Clash_to_Issues.pushbutton/script.py View on GitHub
# -*- coding: utf-8 -*-
"""Turns clash groups and ungrouped clashes into issues, once each, with view, snapshot and elements."""
from pynavis import script, toast

from namo import clashdata, issue_clash, issueapi, issuestore, progress, ui

STATUSES = ['New', 'Active', 'Reviewed', 'Approved', 'Resolved']


def run():
    doc = issuestore.active_doc()
    if doc is None or not issueapi.has_model(doc):
        toast.info('No model open', 'Open the model with the clash tests first.')
        return
    store = issuestore.for_doc(doc)
    if not store.saved:
        toast.info('Save the model first', issuestore.NOT_SAVED)
        return
    names = [clashdata.test_path(t) for t in clashdata.tests(doc)]
    if not names:
        toast.info('No clash tests', 'Set up and run clash tests in Clash Detective first.')
        return
    me = script.bind()
    values = me.get_config(dict(issue_clash.DEFAULTS))
    chosen = [n for n in values.get('tests') or [] if n in names] or names
    fields = [
        {'kind': 'multi', 'key': 'tests', 'label': 'Clash tests', 'options': names, 'default': chosen},
        {'kind': 'multi', 'key': 'statuses', 'label': 'Convert clashes with these statuses',
         'options': STATUSES, 'default': [s for s in values['statuses'] if s in STATUSES]},
        {'kind': 'check', 'key': 'snapshots', 'label': 'Attach the clash image as the snapshot',
         'default': bool(values['snapshots'])},
        {'kind': 'label', 'muted': True,
         'text': 'Each top-level group, and each clash outside a group, becomes one issue. Clashes '
                 'that already have an issue are skipped. Trade, priority and due date follow the '
                 'rules under Shift+Click.'},
    ]

    def check(answers):
        if not answers['tests']:
            return {'tests': 'Tick at least one clash test.'}
        if not answers['statuses']:
            return {'statuses': 'Tick at least one status.'}
        return None

    answers = ui.form('Clash to Issues', fields, ok='Create issues', width=540, validate=check)
    if answers is None:
        return
    values.update(answers)
    me.save_config(values)
    outcome = progress.run('Clash to Issues', lambda report: issue_clash.convert(
        answers['tests'], values, doc, report))
    if outcome is progress.CANCELLED:
        return
    created = len(outcome['created'])
    parts = []
    if outcome['skipped']:
        parts.append('%s already had issues' % '{:,}'.format(outcome['skipped']))
    if outcome['filtered']:
        parts.append('%s had other statuses' % '{:,}'.format(outcome['filtered']))
    if outcome['snapshot_errors']:
        parts.append('%d snapshots failed' % outcome['snapshot_errors'])
    detail = ('; '.join(parts) + '.') if parts else ''
    if created:
        first, last = outcome['created'][0], outcome['created'][-1]
        span = first if created == 1 else '%s to %s' % (first, last)
        toast.success('Created %s issue%s' % ('{:,}'.format(created), '' if created == 1 else 's'),
                      ('%s. %s' % (span, detail)).strip())
    else:
        toast.info('No new issues', detail or 'No clash group or clash matched the chosen statuses.')


run()

config.py

The Shift+Click action.

Coordinate.tab/Issues.panel/Issue_Tools.stack/Clash_to_Issues.pushbutton/config.py View on GitHub
# -*- coding: utf-8 -*-
"""Options for Clash to Issues: priority by penetration depth, due days, template."""
from pynavis import script, toast

from namo import issue_clash, issue_templates, ui

values = script.get_config(dict(issue_clash.DEFAULTS))
due = dict(issue_clash.DEFAULT_DUE)
due.update(values.get('due_days') or {})
bands = issue_clash.bands_of(values.get('bands'))
templates = [t['type'] for t in issue_templates.load()]
order = issue_clash.who_moves(values)
fields = [
    {'kind': 'choice', 'key': 'template', 'label': 'Issue template',
     'options': templates or ['Clash'], 'default': values.get('template') or 'Clash', 'fallback': None,
     'hint': 'Type, number prefix, title pattern and labels come from it.'},
    {'kind': 'label', 'muted': True,
     'text': 'Who moves follows Clash Assigner: %s. The trade lower in that list moves and gets '
             'the issue. Change it with Shift+Click on Clash Assigner.' % ' > '.join(order)},
    {'kind': 'textarea', 'key': 'bands', 'label': 'Priority by penetration depth', 'rows': 3,
     'default': '\n'.join('%g = %s' % (depth, priority) for depth, priority in bands),
     'hint': 'One per line: depth in metres = priority. Shallower clashes and clearance clashes '
             'are Low.'},
    {'kind': 'int', 'key': 'due_critical', 'label': 'Due after (Critical)', 'suffix': 'days',
     'min': 0, 'max': 365, 'default': due.get('Critical')},
    {'kind': 'int', 'key': 'due_high', 'label': 'Due after (High)', 'suffix': 'days', 'min': 0,
     'max': 365, 'default': due.get('High')},
    {'kind': 'int', 'key': 'due_normal', 'label': 'Due after (Normal)', 'suffix': 'days',
     'min': 0, 'max': 365, 'default': due.get('Normal')},
    {'kind': 'int', 'key': 'due_low', 'label': 'Due after (Low)', 'suffix': 'days', 'min': 0,
     'max': 365, 'default': due.get('Low'), 'hint': 'Leave a priority blank for no due date.'},
    {'kind': 'int', 'key': 'max_snapshots', 'label': 'Snapshots per run, at most', 'min': 0,
     'max': 5000, 'default': values.get('max_snapshots'),
     'hint': 'Each clash image takes a moment to render.'},
]


def check(answers):
    pairs, bad = issue_clash.parse_bands(answers['bands'])
    if bad or not pairs:
        return {'bands': 'Write each line as depth = priority, for example 0.05 = High.'}
    return None


answers = ui.form('Clash to Issues options', fields, ok='Save', width=560, validate=check)
if answers is not None:
    values['template'] = answers['template']
    values['bands'] = [[depth, priority] for depth, priority in issue_clash.bands_of(answers['bands'])]
    values['due_days'] = {'Critical': answers['due_critical'], 'High': answers['due_high'],
                          'Normal': answers['due_normal'], 'Low': answers['due_low']}
    values['max_snapshots'] = answers['max_snapshots'] or 0
    script.save_config(values)
    toast.success('Saved Clash to Issues options',
                  'Who moves (from Clash Assigner): %s.' % ' > '.join(order))

Library modules this tool uses

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

  • namo.clashdataOne reader for every clash tool: snapshots, groups, run-to-run comparison, history, images, and the per-result edits that are safe at scale.
  • namo.issue_clashClash results to issues (Clash to Issues) and to BCF topics (BCF Export).
  • namo.issue_templatesIssue templates: what a new RFI, Hold, Verify, Safety or Clash issue starts with.
  • namo.issueapiThe Navisworks half of the issue tools: capture a view as issue material, go back to it, draw issue pins, pick stamp points, and find BCF components in the model.
  • namo.issuestoreWhere Playground issues live: one record per issue, per model file, in the data mode namo.config chooses, plus the session events every issue view listens to.
  • 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.

bundle.yaml

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

Coordinate.tab/Issues.panel/Issue_Tools.stack/Clash_to_Issues.pushbutton/bundle.yaml View on GitHub
title: Clash to Issues
tooltip: Turns each clash group and ungrouped clash of the chosen tests into an issue, once, with its clash view, snapshot and elements, and a trade, priority and due date from rules. Shift+Click for settings.
context: clash-results