Project > Field

QR Tagger

Makes a QR code for each selected object from a payload template (project, file, Element ID, IFC GUID or a URL), saved as PNG files with a printable A4 label sheet, and writes the payload into the object as Playground > QR.

Tab and panelProject > Field
KindButton
Live whenSomething is selected

Status

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

Related asks to confirm

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

Events it listens to

None. It runs only when you click it.

Files

Paths are relative to the Playground.extension folder.

  • Project.tab/Field.panel/QR_Tagger.pushbutton/bundle.yaml
  • Project.tab/Field.panel/QR_Tagger.pushbutton/icon.dark.png
  • Project.tab/Field.panel/QR_Tagger.pushbutton/icon.png
  • Project.tab/Field.panel/QR_Tagger.pushbutton/icon.small.dark.png
  • Project.tab/Field.panel/QR_Tagger.pushbutton/icon.small.png
  • Project.tab/Field.panel/QR_Tagger.pushbutton/script.py

Install

  1. Only this tool. In Navisworks, click pyNavis > Playground, search for QR Tagger, 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. QR Tagger is on the Project tab, Field 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.

Project.tab/Field.panel/QR_Tagger.pushbutton/script.py View on GitHub
# -*- coding: utf-8 -*-
"""Makes a QR code for each selected object: PNG files, a printable label sheet and a Playground > QR property."""
import os

from pynavis import script, toast

from namo import field_qr, fourd_common, items, naming, paths, progress, qr, ui

TITLE = 'QR Tagger'
MAX_OBJECTS = 2000
DEFAULTS = {'template': field_qr.DEFAULT_TEMPLATE, 'level': 'M', 'scale': 8, 'folder': '', 'sheet': True,
            'property': True}
LEVELS = [('L, 7% damage', 'L'), ('M, 15%', 'M'), ('Q, 25%', 'Q'), ('H, 30%', 'H')]


def ask(doc, count, values):
    folder = values.get('folder') or ''
    if not folder or not os.path.isdir(folder):
        folder = fourd_common.doc_folder(doc)
    fields = [
        {'kind': 'text', 'key': 'template', 'label': 'Payload', 'default': values.get('template'),
         'required': True, 'validate': field_qr.check_template,
         'hint': 'Tokens: %s. A URL works too: https://example.com/asset?guid={guid}'
                 % ' '.join('{%s}' % t for t in field_qr.TOKENS)},
        {'kind': 'radio', 'key': 'level', 'label': 'Error correction (how much damage it survives)',
         'options': LEVELS, 'inline': True, 'default': values.get('level', 'M'), 'fallback': 'M'},
        {'kind': 'int', 'key': 'scale', 'label': 'Module size in the PNG files', 'min': 2, 'max': 40,
         'suffix': 'px', 'default': int(values.get('scale') or 8), 'required': True},
        {'kind': 'folder', 'key': 'folder', 'label': 'Save in', 'default': folder, 'required': True,
         'hint': 'A new folder is made inside it for the PNG files and the label sheet.'},
        {'kind': 'check', 'key': 'sheet', 'label': 'Write a printable A4 label sheet',
         'default': bool(values.get('sheet', True))},
        {'kind': 'check', 'key': 'property', 'label': 'Write the payload into each object (Playground > QR)',
         'default': bool(values.get('property', True))},
    ]
    return ui.form(TITLE, fields, intro='%s selected.' % fourd_common.plural(count, 'object'),
                   ok='Make codes', width=600)


def make(objects, answer, folder, doc, report):
    standard = naming.standard_values(doc)
    labels = []
    payloads = []
    for index, obj in enumerate(objects):
        report(0.8 * index / float(len(objects) or 1), 'Encoding %s of %s' % ('{:,}'.format(index + 1),
                                                                         '{:,}'.format(len(objects))))
        values = field_qr.object_values(obj, standard)
        payload, _missing = field_qr.render_payload(answer['template'], values)
        code = qr.encode(payload, answer['level'])
        labels.append({'code': code, 'payload': payload, 'name': values['name'],
                       'element_id': values['element_id'],
                       'lines': ['ID %s' % values['element_id'] if values['element_id'] else '',
                                 values['guid']]})
        payloads.append(payload)
    paths.ensure_dir(folder)
    for label, name in zip(labels, field_qr.file_names(labels)):
        qr.write_png(label['code'], os.path.join(folder, name), scale=answer['scale'])
        label['file'] = name
    sheet = None
    if answer['sheet']:
        report(0.85, 'Writing the label sheet')
        sheet = os.path.join(folder, 'QR labels.html')
        rows = [{'svg': qr.to_svg(l['code']), 'name': l['name'], 'lines': l['lines']} for l in labels]
        paths.atomic_write_text(sheet, field_qr.sheet_html(
            rows, 'QR labels', '%s. Print at 100%% scale on A4.' % fourd_common.plural(len(rows), 'label')))
    written, errors = 0, []
    if answer['property']:
        report(0.9, 'Writing the Playground > QR property')
        written, errors = field_qr.write_property(objects, payloads, doc)
    return {'labels': labels, 'sheet': sheet, 'written': written, 'errors': errors}


def run():
    if ui.is_runner():
        toast.info('%s works on the selection' % TITLE, 'Run it from the ribbon.')
        return
    doc = fourd_common.active_doc()
    # Stop the walk one past the limit: a whole-model selection must not be walked in full
    # just to be refused.
    objects = items.objects(items.selected(doc), budget=MAX_OBJECTS + 1)
    if not objects:
        toast.info('Nothing selected', 'Select the objects to tag, then run QR Tagger.')
        return
    if len(objects) > MAX_OBJECTS:
        toast.info('Too many objects selected', 'Select at most %s objects; more are selected.'
                   % '{:,}'.format(MAX_OBJECTS))
        return
    values = script.get_config(DEFAULTS)
    answer = ask(doc, len(objects), values)
    if answer is None:
        return
    values.update(dict((k, answer[k]) for k in ('template', 'level', 'scale', 'folder', 'sheet', 'property')))
    script.save_config(values)
    folder = field_qr.output_folder(answer['folder'], fourd_common.doc_title(doc))
    try:
        result = progress.run(TITLE, lambda report: make(objects, answer, folder, doc, report))
    except qr.QRError as error:
        toast.error('A payload is too long for a QR code', '%s Shorten the payload template.' % error)
        return
    except (IOError, OSError) as error:
        toast.error('Could not write the QR files', str(error))
        return
    if result is progress.CANCELLED:
        return
    largest = max(l['code'].version for l in result['labels'])
    made = 'Made %s' % fourd_common.plural(len(result['labels']), 'QR code')
    detail = 'PNG files%s in %s. Largest code: version %d.' % (
        ' and a label sheet' if result['sheet'] else '', folder, largest)
    repeats = field_qr.repeated_payloads([l['payload'] for l in result['labels']])
    if result['errors']:
        toast.warning('%s; %s not written' % (made, fourd_common.plural(len(result['errors']), 'property',
                                                                        'properties')),
                      '%s The files are in %s.' % (result['errors'][0], folder))
    elif repeats:
        toast.warning(made, '%s the same payload as another: the template has nothing that tells them '
                      'apart (no Element ID or GUID). Files in %s.'
                      % ('1 object has' if repeats == 1 else '%s objects have' % '{:,}'.format(repeats), folder))
    else:
        toast.success(made, detail)
    fourd_common.open_file(result['sheet'] or folder)


run()

Library modules this tool uses

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

  • namo.field_qrQR Tagger: payloads from a template, PNG files, a printable label sheet and the 'Playground > QR' property.
  • namo.fourd_commonHelpers the 4D, Quantities and Field tools share: scopes, default files, text.
  • namo.itemsModel items: walking, collecting, identifying, measuring and showing them.
  • namo.namingName templates: '{test} - {n:000}', '{date:%Y%m%d} {project}', '{{literal braces}}'.
  • namo.pathsWhere Playground keeps its files, and how it writes them safely.
  • namo.progressGlue between pyNavis' cancellable progress window and Playground's progress callbacks.
  • namo.qrA pure QR Code encoder (ISO/IEC 18004:2015), PNG and SVG output.
  • 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.

Project.tab/Field.panel/QR_Tagger.pushbutton/bundle.yaml View on GitHub
title: QR\nTagger
tooltip: Makes a QR code for each selected object from a payload template (project, file, Element ID, IFC GUID or a URL), saved as PNG files with a printable A4 label sheet, and writes the payload into the object as Playground > QR.
context: selection