Skip to content

Write a plugin for Dentra Viewer

A plugin adds features to the Viewer. You write it, we test it, and if we like it everyone finds it in the «Plugins» panel.

Introduction

Plugins are written by the community: they are for anyone who wants to customise the Viewer or add features to it. We provide the environment and this documentation.

A plugin is a small web page (HTML, JavaScript and CSS) that the Viewer opens in its own frame. From the frame the plugin talks to the Viewer through the DentraViewer kit, which is already loaded.

The plugin runs in a closed frame: it does not see the page, the account or Dentra's data. It can only do what it asks for in its permissions, and the dentist reads them before turning it on.

What you can build

  • New ways to control the view: hand gestures in front of the webcam, game controllers, voice commands.
  • Measurements and annotations: clicks on the model, points, lines and labels in the scene.
  • Model analysis: mesh checks, calculations, results saved to a file.
  • Connections to other services: models brought in from outside, the phone as a remote.

Getting started

  1. 1

    Become a developer

    You need a Dentra account. In /app, under «Developer», request access: we approve it.

  2. 2

    Start from the example

    Download the example plugin: four arrows that turn the model. It has everything you need.

  3. 3

    Write your own

    Change dentra-plugin.json (id, name, permissions) and the page. The DentraViewer kit is already there: no import needed.

  4. 4

    Try it in the Viewer

    Zip the folder and upload it from your Developer area with «Upload as draft»: only you can see it and it is not sent for review. «Try in Viewer» opens the Viewer with your plugin turned on, on two sample arches or on your own models. Changed something? Upload the zip again with the same version number and try again.

  5. 5

    Submit it

    When it works, press «Submit for review» next to the draft. If something in the zip is wrong, we tell you right away, as soon as you upload it.

The zip structure

my-plugin.zip
├── dentra-plugin.json
├── index.html        ← "entry"
├── plugin.js
├── style.css
└── icon.png          ← "icon" (optional)

The zip can only contain these file types: html, js, mjs, css, json, wasm, png, jpg, jpeg, svg, webp, gif, ico, woff, woff2, txt, md, task, tflite, bin, data, onnx. At most 400 files and 120 MB once unpacked; the zip itself can be up to 40 MB.

dentra-plugin.json

The file sits at the root of the zip and says who the plugin is and what it needs.

{
  "id": "click-markers",
  "name": "Markers",
  "version": "1.0.0",
  "description": "Places a numbered marker wherever you click on the model.",
  "author": {
    "name": "Your name",
    "email": "[email protected]",
    "website": "https://example.com"
  },
  "entry": "index.html",
  "permissions": [
    "pick",
    "draw",
    "toolbar"
  ],
  "reasons": {
    "pick": "To place a marker where you click.",
    "draw": "To draw the marker on the model.",
    "toolbar": "A button to clear the markers."
  },
  "platforms": [
    "web",
    "desktop"
  ],
  "api": 1
}
id
The technical name: 3-40 characters among lowercase letters, digits and dashes, starting and ending with a letter or digit. It belongs to the account that uploads it first, even as a draft: no one else can use it.
name
The name the dentist sees, up to 40 characters.
version
Three numbers, e.g. 1.0.0. Raise it for a new version: a number already submitted for review is rejected, while a draft can be uploaded again with the same number.
description
One line on what it does, up to 300 characters.
author
Your name; email and website are optional.
entry
The HTML page the Viewer opens in the frame. If missing, index.html.
permissions
What it asks the Viewer for: see «Permissions».
reasons
For each permission (except view), one line on why, up to 160 characters. The dentist reads it before turning it on.
platforms
Where it works: web, desktop or both. If missing, both. With internet and models together, desktop only.
send
Required only if you ask for internet and models together: to (the domain that receives the scans, e.g. api.example.com, without https://) and why.
icon
Optional: a .png, .svg or .webp inside the zip.
api
The kit version: 1 today.

Permissions

Declared in the permissions field. Each permission is a single thing. Before turning the plugin on, the dentist reads what it asks for, with the reasons you wrote in reasons.

view
turn, move and zoom the model, put the view back as it was, know where it is looking.
pick
know where the dentist clicks on the model: point, surface direction and which model.
draw
draw points, lines, labels and models brought by the plugin (STL, PLY, OBJ) in the scene.
edit
hide, colour or make the open models transparent.
models
read the files of the open models.
files
save a file on the dentist's computer.
toolbar
put up to 4 buttons in the Viewer bar.
camera
receive images from the camera. The Viewer opens it and asks the dentist for permission; the plugin only gets the images.
microphone
receive audio from the microphone. The Viewer opens it and asks the dentist for permission.
internet
connect to the internet. Without it, the plugin cannot connect to any other site.
clipboard
copy a text to the clipboard.

Web and desktop app

A plugin can work in the Viewer on the web (web), in the desktop app (desktop) or in both. You declare it in the platforms field, and we show it in the list and on the plugin page. Today the plugins panel is in the Viewer on the web; for now, the desktop app doesn't have it.

Internet and models together mean the plugin can send patients' scans out. This is only possible in the desktop app, where the files belong to the dentist, and only by declaring in the send field where they go and why. The dentist reads it plainly before turning it on; on the web such a plugin cannot be turned on.

"platforms": ["web", "desktop"]

// internet + models: desktop only, and you must say where the scans go
"permissions": ["internet", "models"],
"send": { "to": "api.example.com", "why": "Analyses the scan and returns a report." }

Kit reference

Your plugin's entry page already has window.DentraViewer: the Viewer loads it for you. Functions written with await return a Promise; the others do not wait for an answer. Each function needs the permission shown; without it, the Viewer ignores it or the Promise fails with an error. DentraViewer only exists inside the Viewer: if you open the plugin page on its own, from your computer, it isn't there.

await DentraViewer.ready()

waits for the Viewer; returns { language, permissions, api }: the user's language, the permissions granted and the kit version.

DentraViewer.view.move({ rotate: { x, y, z }, pan: { x, y }, zoom })

rotate in radians around the screen axes (x up and down, y left and right, z spin in place), pan in fractions of the model size, a positive zoom gets closer (0.1 = 10%). Each value counts for at most 0.5 per call.

Permission: view

DentraViewer.view.reset()

puts the view back as it was.

Permission: view

await DentraViewer.view.get()

where the camera is looking now: position and target, each [x, y, z].

Permission: view

DentraViewer.pick.onClick(({ point, normal, model }) => …)

on every click on the model (not while dragging): point and normal as [x, y, z], and model, the id of the clicked model or null.

Permission: pick

DentraViewer.scene.draw(key, { shape, … })

draws a shape: shape "points" (points, color, size), "line" (points, color, closed), "text" (point, text, color, size) or "mesh" (data, format stl/ply/obj, color, opacity). Coordinates are the same as pick.onClick. The same key replaces it. Up to 500 shapes, a mesh up to 100 MB.

Permission: draw

DentraViewer.scene.remove(key) / DentraViewer.scene.clear()

removes one shape, or all of the plugin's shapes.

Permission: draw

await DentraViewer.models.list()

the open models: id, name, format.

Permission: models

await DentraViewer.models.read(id)

a model's file: data is an ArrayBuffer.

Permission: models

DentraViewer.models.change(id, { visible, opacity, color })

shows or hides (visible), changes the opacity (opacity, 0 to 1) or the colour (color, "#rrggbb"; null goes back to the previous colour) of an open model.

Permission: edit

DentraViewer.files.save(name, data, type)

downloads a file to the computer (string or ArrayBuffer, up to 200 MB); type is the file type, e.g. "text/csv".

Permission: files

DentraViewer.toolbar.button(key, label) / toolbar.onPress((key) => …) / toolbar.remove(key)

a button in the Viewer bar (up to 4, label up to 24 characters), what happens when it is pressed, and toolbar.remove(key) to remove it.

Permission: toolbar

DentraViewer.camera.start({ fps, width }) / camera.onFrame((image, t) => …)

turns the camera on: fps 1 to 30 (default 15), width 160 to 1280 pixels (default 640). Images arrive as ImageBitmap; camera.onStatus tells you whether it is on or why not. camera.stop() turns it off.

Permission: camera

DentraViewer.microphone.start() / microphone.onAudio((samples, sampleRate) => …)

turns the microphone on; audio arrives in Float32Array chunks, with the sample rate. microphone.onStatus tells you whether it is on or why not; microphone.stop() turns it off.

Permission: microphone

await DentraViewer.storage.get() / DentraViewer.storage.set(value)

one JSON value stored on this device, for your plugin only, up to 100 KB: anything larger is not saved. get() returns null if there is nothing.

DentraViewer.clipboard.write(text)

copies a text to the clipboard, up to 2000 characters.

Permission: clipboard

DentraViewer.panel.height(px)

the height of the plugin frame in pixels, from 80 to 560 (default 180).

A complete example

// plugin.js: place a numbered marker wherever the dentist clicks
// Permissions in dentra-plugin.json: "pick", "draw", "toolbar" (the manifest above)
let n = 0;

DentraViewer.ready().then(({ language, permissions }) => {
  // language: the dentist's language ("it", "en", "de"…), to translate your labels
  DentraViewer.panel.height(120);
  DentraViewer.toolbar.button("clear", "Clear");
});

DentraViewer.pick.onClick(({ point }) => {
  n++;
  DentraViewer.scene.draw("dot-" + n, { shape: "points", points: [point], color: "#e5484d", size: 0.6 });
  DentraViewer.scene.draw("label-" + n, { shape: "text", point: [point[0], point[1] + 2, point[2]], text: "#" + n });
});

DentraViewer.toolbar.onPress((key) => {
  if (key === "clear") { DentraViewer.scene.clear(); n = 0; }
});

Review and publishing

  1. You submit the zip from your Developer area, or press «Submit for review» on a draft you have already tried. We check the files and dentra-plugin.json as soon as you upload the zip, drafts included, and tell you what is missing. When it enters review, you get a confirmation email.
  2. We test it in the Viewer before publishing it. If it reads the models and uses the internet, it also goes through a privacy check.
  3. If we approve it we publish it: it shows up in the public plugin list and in the Viewer panel. If not, we write to you about what to change, and in your Developer area that version is marked «Needs changes», with our note. Either way you get an email.
  4. For a new version, even after a rejection, raise the version number and submit it again: a number already submitted can't be reused. A draft, instead, you upload again with the same number until you submit it. It goes through the same test, and until we approve it the previous one stays in the Viewer. Once we approve it, whoever has already turned the plugin on gets the new version automatically. If it asks for permissions the previous version did not, though, whoever had turned it on finds it paused: the Viewer shows them the new permissions with your reasons, and the plugin starts again only once they accept.

Rules

  • Plugins are free for the people who use them.
  • You need a Dentra account with Developer access, which we approve. Then we test every plugin, one by one.
  • The plugin runs in its own closed frame: it does not see the page, the accounts or Dentra's data.
  • Every permission needs a one-line reason.
  • Scans leave only in the desktop app, declaring in send where they go and why.
  • By submitting it you agree that we test it and, if we like it, publish it for free in the Viewer. The plugin stays yours.
  • We may withdraw a plugin from the catalogue: for a security problem we do it immediately, without waiting. We email you the reason and you find it in your Developer area; whoever had turned it on finds it off.

Become a developer

You need a Dentra account, with billing details like every account. From your account you request Developer access; once we approve it, you submit plugins from your area and see where they stand.

Developer access is the start: tomorrow Dentra's APIs will go through it too.