Project > Exchange

Geometry Exporter

Exports the selected objects' own triangles with their colours to glTF (.glb), OBJ, STL or DXF 3D faces, adding IDs and chosen properties in glTF. Big selections are budgeted and anything skipped is listed.

Tab and panelProject > Exchange
KindButton
Live whenSomething is selected

Status

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

The public ask

I am trying to export the vertices, faces and colors of a selected object in the Navis.

Stack Overflow, 2015-07. Open the source

Quoted from the page itself.

Events it listens to

None. It runs only when you click it.

Files

Paths are relative to the Playground.extension folder.

  • Project.tab/Exchange.panel/Geometry_Exporter.pushbutton/bundle.yaml
  • Project.tab/Exchange.panel/Geometry_Exporter.pushbutton/icon.dark.png
  • Project.tab/Exchange.panel/Geometry_Exporter.pushbutton/icon.png
  • Project.tab/Exchange.panel/Geometry_Exporter.pushbutton/icon.small.dark.png
  • Project.tab/Exchange.panel/Geometry_Exporter.pushbutton/icon.small.png
  • Project.tab/Exchange.panel/Geometry_Exporter.pushbutton/script.py

Install

  1. Only this tool. In Navisworks, click pyNavis > Playground, search for Geometry Exporter, 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. Geometry Exporter is on the Project tab, Exchange 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/Exchange.panel/Geometry_Exporter.pushbutton/script.py View on GitHub
# -*- coding: utf-8 -*-
"""Exports the selected objects' triangles to glTF (.glb), OBJ, STL or DXF 3D faces."""
import os

from pynavis import doc as nwdoc
from pynavis import script, toast

from namo import items, meshexport, paths, progress, ui, xchg_common

NAME_TEMPLATE = '{file} - Geometry - {date}'


def property_options(selected):
    """Chooser options for the property names on the first objects of the selection."""
    try:
        sample = items.objects(selected, budget=150)
        names = items.sample_properties(sample, limit=150)
    except Exception:
        return []
    return [meshexport.property_option(cat, prop) for cat, prop, _n in names][:400]


def run():
    if xchg_common.under_runner():
        return
    document = nwdoc.get_doc()
    selected = items.selected(document)
    if not selected:
        toast.info('Nothing selected', 'Select the objects to export, then run Geometry Exporter again.')
        return
    doc_units = str(document.Units)
    values = dict(meshexport.DEFAULT_OPTIONS)
    values.update(script.get_config(meshexport.DEFAULT_OPTIONS))
    fields = meshexport.form_fields(values, doc_units, property_options(selected))
    answer = ui.form('Geometry exporter', fields, ok='Export', width=560)
    if answer is None:
        return
    values.update(answer)
    script.save_config(dict((k, values[k]) for k in meshexport.DEFAULT_OPTIONS))
    fmt = values['format']
    ext = meshexport.EXTENSIONS[fmt]
    path = xchg_common.ask_save(document, NAME_TEMPLATE, ext, meshexport.FILTERS[fmt], 'Export geometry')
    if path is None:
        return
    pairs = meshexport.property_pairs(values.get('properties'))
    title = xchg_common.doc_title(document)
    y_up = fmt == 'glb' or (fmt == 'obj' and values['obj_y_up'])

    def work(report):
        read = meshexport.read_objects(selected, per_object=values['per_object'], total=values['total'],
                                       progress=lambda f, label: report(f * 0.85, label),
                                       identity=values['identity'] and fmt == 'glb',
                                       property_pairs=pairs if fmt == 'glb' else None, doc_units=doc_units)
        records = read['records']
        if not records:
            return read, [], [], []
        shift = meshexport.local_origin(meshexport.union_box(records), doc_units) if values['shift'] else None
        report(0.87, 'Preparing %s objects' % '{:,}'.format(len(records)))
        meshes, dropped = meshexport.build_meshes(records, fmt, doc_units, shift, values['units'],
                                                  values['obj_y_up'])
        _scale, word = meshexport.target_units(fmt, values['units'], doc_units)
        header = ['Source: %s' % title, 'Units: %s' % word, 'Up axis: %s' % ('Y' if y_up else 'Z')]
        extras = {'source': title, 'units': 'metres', 'up': 'Y', 'model_units': doc_units}
        if shift:
            header.append('Local origin subtracted (model units): %s' % ', '.join(
                meshexport.num_text(v, 12) for v in shift))
            extras['local_origin_model_units'] = list(shift)
        report(0.94, 'Writing %s' % os.path.basename(path))
        written = meshexport.write_files(path, fmt, meshes, title=title, extras=extras,
                                         layer_by=values['layers'], header_lines=header)
        if shift:
            note = meshexport.offset_note(shift, doc_units, fmt, word, title, axes_y_up=y_up)
            written.append(paths.atomic_write_text(meshexport.sidecar_path(path), note))
        report(1.0, 'Done')
        return read, meshes, dropped, written

    try:
        outcome = progress.run('Exporting geometry', work)
    except (IOError, OSError) as error:
        toast.error('Could not write the file', '%s. Close it in any program that has it open, then try again.' % error)
        return
    if outcome is progress.CANCELLED:
        return
    read, meshes, dropped, written = outcome
    xchg_common.show_skipped('Geometry exporter', read['skipped'], dropped)
    level, message, detail = meshexport.summary(meshes, read['skipped'], dropped, fmt, read['sampled'])
    if written:
        # Say where the file is whatever else there is to say, and show it in Explorer.
        detail = written[0] if level == 'success' else xchg_common.where_text(detail, written[0])
    toast.show(level, message, detail)
    if written:
        xchg_common.reveal(written[0])


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.meshexportGeometry out of Navisworks: glTF 2.0 binary (.glb), OBJ + MTL, binary STL and DXF 3D faces, from the selected objects' own triangles.
  • 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.uiWPF building blocks for Playground tools, themed from the pyNavis design system.
  • namo.xchg_commonSmall pieces the Geometry and IFC exporters share: where files go, default names, and the list of skipped objects in the output window.

bundle.yaml

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

Project.tab/Exchange.panel/Geometry_Exporter.pushbutton/bundle.yaml View on GitHub
title: Geometry\nExporter
tooltip: Exports the selected objects' own triangles with their colours to glTF (.glb), OBJ, STL or DXF 3D faces, adding IDs and chosen properties in glTF. Big selections are budgeted and anything skipped is listed.
context: selection