Project > Field
Photo Pins
Pins site photos to a point in the model, with a caption and the level, grid and object found there, and draws the pins in the view. Click the point, choose the photos, then type a caption. Shift+Click for settings.
Status
Launched Ran inside Navisworks at least once with no error logged; nobody checked the result yet.
First field click found the pin landing away from the marker; refit to the host Measure pick on 2026-10-05 and not run since.
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
It needs these Navisworks event hooks, so installing it also copies the shared hooks folder:
doc-closeddoc-opened
Files
Paths are relative to the Playground.extension folder.
Project.tab/Field.panel/Photo_Pins.pushbutton/bundle.yamlProject.tab/Field.panel/Photo_Pins.pushbutton/config.pyProject.tab/Field.panel/Photo_Pins.pushbutton/icon.dark.pngProject.tab/Field.panel/Photo_Pins.pushbutton/icon.pngProject.tab/Field.panel/Photo_Pins.pushbutton/icon.small.dark.pngProject.tab/Field.panel/Photo_Pins.pushbutton/icon.small.pngProject.tab/Field.panel/Photo_Pins.pushbutton/script.py
Install
- Only this tool. In Navisworks, click pyNavis > Playground, search for Photo Pins, tick it and click Install.
- The whole extension. Download
Playground.extension.zip, unzip it, put thePlayground.extensionfolder in%APPDATA%\pyNavis\extensions\, and click pyNavis > Reload. Photo Pins 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.
ReportSource
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/Photo_Pins.pushbutton/script.py View on GitHub# -*- coding: utf-8 -*-
"""Pins site photos to a point you click in the model, with a caption, the level, grid and object there."""
import os
from pynavis import forms, toast
from namo import camera, config as namo_config, field_photobrowser, fourd_common, items, photopins, picking, ui
TITLE = 'Photo Pins'
def linked_object(hit, doc):
"""(entries, name) of the object under the clicked point, ([], '') when there is none."""
if hit.item is None:
return [], ''
try:
element = items.object_of(hit.item)
return items.entries([element], doc), str(element.DisplayName or '') or items.category(element)
except Exception:
return [], ''
def ask():
"""(photo files, caption) for the new pin, or None when the user backs out."""
files = photopins.choose_photos('Photos to pin here')
if not files:
return None
default = os.path.splitext(os.path.basename(files[0]))[0]
caption = forms.ask_string('Caption for %s:' % fourd_common.plural(len(files), 'photo'), default=default,
title=TITLE)
if caption is None:
return None
return files, caption
def run():
if ui.is_runner():
toast.info('%s needs a person at the screen' % TITLE, 'Run it from the ribbon.')
return
doc = fourd_common.active_doc()
if not fourd_common.has_models(doc):
toast.info('No model is open', 'Open or append a model first.')
return
hit = picking.point('Click where the photos were taken', want_item=True, doc=doc)
if hit is None:
return
view_camera = camera.capture(doc)
photopins.preview(hit.point, doc) # the pin shows where it will land while you choose
try:
answer = ask()
finally:
photopins.clear_preview()
if answer is None:
return
files, caption = answer
level, grid = photopins.location_labels(hit.point, doc)
item_entries, item_name = linked_object(hit, doc)
record = photopins.new_pin(hit.point, str(doc.Units), [], caption, level, grid, item_entries, item_name,
view_camera, namo_config.user_name())
records = photopins.store(doc)
try:
saved = photopins.add_pin(records, record, files, doc)
except (IOError, OSError, ValueError) as error:
toast.error('Could not pin the photos', str(error))
return
place = ', '.join(p for p in (level, 'grid %s' % grid if grid else '', item_name) if p)
detail = (place + '. ' if place else '')
shown = photopins.settings().get('show_pins', True)
drawn = photopins.refresh(doc)
browser_open = field_photobrowser.refresh_open(records.list())
if not shown:
detail += 'Pins are hidden: Shift+Click Photo Pins to show them.'
elif not drawn:
detail += 'The pin is saved but could not be drawn in the view. Photo Browser lists it.'
elif not browser_open:
detail += 'Open Photo Browser to see them.'
if records.note:
detail = records.note + ' ' + detail
toast.success('Pinned %s as %s' % (fourd_common.plural(len(saved['photos']), 'photo'), saved['id']),
detail.strip() or None)
run()
config.py
The Shift+Click action.
Project.tab/Field.panel/Photo_Pins.pushbutton/config.py View on GitHub# -*- coding: utf-8 -*-
"""Shows or hides the photo pins in the view and picks their size and what their labels say."""
from pynavis import toast
from namo import fourd_common, photopins, ui
def run():
if ui.is_runner():
return
values = photopins.settings()
fields = [
{'kind': 'check', 'key': 'show_pins', 'label': 'Show photo pins in the view',
'default': bool(values.get('show_pins', True)),
'hint': 'Pins are redrawn whenever a model with pins opens.'},
{'kind': 'radio', 'key': 'label', 'label': 'Pin labels show', 'inline': True,
'options': [('Caption', 'caption'), ('Pin number', 'id')], 'default': values.get('label', 'caption'), 'fallback': 'caption'},
{'kind': 'radio', 'key': 'size', 'label': 'Pin size', 'inline': True,
'options': [(label, key) for key, label, _metres in photopins.PIN_SIZES],
'default': values.get('size', 'medium'), 'fallback': 'medium',
'hint': 'The marker is 0.2 m across when small, 0.4 m medium, 1 m large.'},
]
answer = ui.form('Photo Pins settings', fields, ok='Save')
if answer is None:
return
photopins.save_settings({'show_pins': bool(answer['show_pins']), 'label': answer['label'],
'size': answer['size']})
doc = fourd_common.active_doc()
if not answer['show_pins']:
photopins.clear_drawn()
toast.success('Photo pins hidden', 'They stay saved, and Photo Browser still lists them.')
return
if not fourd_common.has_models(doc):
toast.success('Photo pins will show', 'Open a model with pins to see them.')
return
total = len(photopins.store(doc).list())
drawn = photopins.refresh(doc)
if total and not drawn:
toast.warning('Photo pins could not be drawn', 'The view overlay is not loaded. Restart Navisworks, '
'then try again.')
elif not total:
toast.success('Photo pins shown', 'This model has no pins yet. Click Photo Pins to add one.')
else:
toast.success('Photo pins shown', '%s in this model.' % fourd_common.plural(drawn, 'pin'))
run()
Library modules this tool uses
From the shared namo library, which installs with every tool. Each name opens the module on GitHub.
namo.cameraThe Navisworks camera, saved viewpoints and section planes, as plain data.namo.configPlayground-wide settings: one JSON file shared by every Playground tool.namo.field_photobrowserPhoto Browser: a modeless window of photo-pin thumbnails grouped by level, grid or linked object, with a filter, Go to, Open photo, Delete pin and an HTML photo report.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.photopinsPhoto Pins and the Photo Browser: site photos pinned to points in the model.namo.pickingThe one way a Playground tool asks for a click in the 3D view.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/Photo_Pins.pushbutton/bundle.yaml View on GitHubtitle: Photo\nPins
tooltip: Pins site photos to a point in the model, with a caption and the level, grid and object found there, and draws the pins in the view. Click the point, choose the photos, then type a caption. Shift+Click for settings.
context: doc