# WAZO3D plugins

A plugin asks for a few settings, shows the result in the view while they change, and puts it in the model as
one group, undone in one step. `gear.js` (a spur gear with involute teeth), `wheel.js` (a model railway
wheel) and `text.js` (letters in WAZO3D's fonts) are complete examples. Read them alongside this page.

## Developing a plugin

The quickest way is WAZO3D's own **plugin editor**: a code editor in a window of its own, made for this.

1. Install it: on the site under **Add-ons** (*Plugin editor*, free), or switch it on under **Tools › My add-ons**.
   A code icon appears in the ribbon, and **Tools › Plugin editor** opens it as well.
2. It opens a window beside the editor (not a tab). **New…** asks a name and what to start from: an empty plugin, a
   block with a hole, or a copy of one of the examples. The name becomes the plugin's name and its id (`me.…`); every
   project is kept in this browser and chosen at the top left. The sun or moon switches between light and dark.
3. Write. It knows this API: it completes what you type and marks mistakes as you make them (it reads
   `wazo-plugin.d.ts`). **Ctrl+F** finds, **Ctrl+H** replaces, the list on the left holds every function, class,
   method and variable and jumps to it, and **Ctrl+Shift+O** does the same from the keyboard.
4. **Commit** (**Ctrl+Enter**): the plugin is kept in this browser, switched on, and its dialog opens in the editor
   on the new code at once. Change, commit, look: that is the whole loop.
5. **Check** runs it the way the site does before it publishes one: at its defaults and every preset, saying what it
   made, how long it took, which plan placing it needs and anything worth a word.
6. **Save file** writes the `.js` file. Publishing is the site's: in the editor, **Tools › My add-ons** has a publish
   button beside a plugin of your own, and it waits on the site under *My add-ons* until you publish it there.

**Files that belong to a plugin.** A plugin is one file, so what it needs goes into the code. **Insert a file** puts a
file in **at the foot of the code**, out of the way, as a function named after it that returns it: text (`.txt`,
`.csv`…) as a string, `.json` as a value, anything else as its bytes (at most 96 kB; a plugin is at most 256 kB), and
an **SVG as an icon**: its paths, ready for `button: PAAL()`. A declared function is there before the code above it
runs, so the info at the top can already call it. Pictures and fonts are better not put in: a plugin paints with
WAZO3D's standard textures (`texture: {series, id}`) and lets the user choose a font (a `font` setting), which keeps it
small. Files the *user* chooses each time are a `file` setting instead (below).

Any other editor works too: write the file, then in the WAZO3D editor open **Tools › My add-ons**, press the file
button and choose it. Choosing it again after a change replaces it.

A plugin may put **a button of its own on the ribbon**, which opens its dialog: `button: 'cog'`, one of the names in
`PluginIcon` in `wazo-plugin.d.ts`, or an icon of its own as SVG path data in a 24 × 24 box,
`button: { paths: ['M12 2v20', 'M6 8h12'] }`, drawn stroked like the editor's own icons. Only path data comes across,
never markup.

## The file

A plugin is **one JavaScript module**. Its default export describes the plugin:

```js
export default {
  id: 'yourname.thing',            // small letters, digits, dots and dashes; your own
  version: '1.0.0',
  author: 'Your name',
  name: { en: 'Thing', nl: 'Ding', de: 'Ding', es: 'Cosa' },   // or one string; English stands in for a language left out
  description: { en: 'What it makes, in a sentence.' },
  button: 'cog',                   // optional: a ribbon button that opens it
  params: [ /* the settings, below */ ],
  build(values) { return [ /* the steps, below */ ]; },
};
```

`wazo-plugin.d.ts` describes the same in TypeScript, for editors that read it.

### Settings (`params`)

Each setting has an `id` and a `label`, and optionally a `hint` (shown when the pointer rests on it). It is one
of five kinds:

| type | fields | the value `build` receives |
|---|---|---|
| `number` | `default`, `min`, `max`, `step`, `integer`, `unit: 'mm' \| 'deg' \| ''`, `slider` | a number, kept between `min` and `max` |
| `choice` | `default`, `options: [{ value, label }]` | the chosen option's `value` |
| `boolean` | `default` | `true` or `false` |
| `text` | `default`, `maxLength` (100 by default, at most 2000), `multiline` | the text as typed |
| `font` | `default`: one of WAZO3D's font ids (below) | the font's id, or the id of a font of the user's own |
| `file` | `accept`: the extensions it takes, such as `['csv', 'txt']` | the kept file's id, or `''` for none; the file itself in `wazo.files` |

A number with `unit: 'mm'` is shown and typed in the user's own unit (cm, m, inches…), but always reaches
`build` in millimetres. `slider: true` puts a slider beside the field; it needs a `min` and a `max`.

### The dialog

You describe the dialog; WAZO3D draws it, in its own look and in the user's language. A plugin never puts
anything on the page itself. That is part of what keeps it sealed off.

- **Sections.** `sections: [{ id, label, hint?, open? }]` are headings. A setting stands in one with
  `section: 'id'`; settings without one stand above them. A section is folded shut unless `open: true`, and a
  section whose settings are all out of sight is hidden.
- **Settings that take turns.** `when: { style: 'spokes' }` shows a setting only while `style` holds
  `'spokes'`; `when: { style: ['disc', 'holes'] }` while it holds either. Name several settings and all must
  hold. A hidden setting keeps its value, and `build` still receives it.
- **Settings for hosts only.** `host: true` is never shown in WAZO3D's dialog; there `build` gets its default.
  A host building the plugin by name (the silent builder) may set it, as the catenary masts' `detail` is set.
- **Presets.** `presets: [{ id, label, values: { module: 1, teeth: 12 } }]` puts a choice at the top. Picking
  one fills in the settings it names and leaves the rest. Changing a value by hand shows *Your own values*.
  A preset may say `when: { scale: 'H0' }`: it is offered only while that holds, and the choice of presets then stands
  right after the settings its conditions name, so a scale chosen first gets its own presets.
- **Figures and notes.** `build` may return `{ steps, figures, notes }` instead of the steps alone.
  `figures: [{ label, value, unit? }]` are shown under the settings, a length in the user's unit; `notes` are
  words of warning or advice.
- **Tiles on a grid.** A plugin that makes a tile of a modular system (a dungeon floor, a piece of marble
  run) adds `grid: { system: 'OpenForge', shape: 'square', cell: 25.4, rise: 12.7 }`. What is placed then
  clicks on that grid: moved, copied, pasted or turned, it lands on the grid of the nearest tile of the same
  system and turns a quarter (hexagons: a sixth) of a turn at a time. Put a corner of the tile's cells on the
  origin (hexagons: the middle of a cell, flats facing ±x), the grid in x–z with y up. `rise` is the height
  of one level; leave it out to keep every tile on its neighbour's level. The user can change the grid in
  the properties, and is told when a tile changed inside no longer fits its cells.

`gear.js` uses all of these: sections, a hub length shown only with the hub, a slider for the teeth, presets,
the circles it worked out, and a note under 17 teeth. `wheel.js` shows two settings taking turns (spokes or
holes) and the sizes the wheel comes to when printed.

### Letters (`font` and `text`)

A plugin cannot read a font file: it sees nothing outside itself. So WAZO3D reads the font a `font` setting
chose and hands it to `build` as its second argument, holding every character of the plugin's `text` settings:

```js
build(values, wazo) {
  const font = wazo.fonts[values.font];
  // font.glyphs['B'] = { advance, shapes: [{ outline: [[x, y], …], holes: [[[x, y], …], …] }] }
  // font.kerning['AV'], font.capHeight, font.ascender, font.descender, font.missing
}
```

Everything in it is in **ems** (1 em is the font's size), y up, the baseline at nought. Each glyph comes already
sorted into shapes, an outline with its holes, ready for an `extrude` step. Laying out a text is then plain
arithmetic: move a pen along, place each glyph's shapes where the pen stands, and move on by its `advance` plus
the kerning with the next character. `text.js` does exactly that, with lines, alignment and spacing.

WAZO3D's fonts are Barlow (`sans`, `sans-bold`), Roboto Slab (`serif`, `serif-bold`), JetBrains Mono (`mono`),
Allerta Stencil (`stencil`), Patrick Hand (`hand`), Oswald (`condensed`, `condensed-bold`), Bebas Neue (`display`),
Nunito (`rounded`, `rounded-bold`), Pacifico (`script`), UnifrakturMaguntia (`gothic`), Special Elite (`typewriter`)
and Poiret One (`deco`): Latin, and extended Latin for all but the stencil and the gothic. A user may also load a font
of their own (TrueType, OpenType or WOFF) in the setting itself; it is kept in their browser and reaches the plugin in
just the same shape, under an id of its own (`file:…`), so a plugin never needs to know which it was.

### Files of the user's own (`file`)

A `file` setting lets the user choose a file from their computer for the plugin to read: a table of sizes, a profile,
an outline. It is kept in their browser, never sent anywhere, and the plugin is handed it in its second argument:

```js
build(values, wazo) {
  const table = wazo.files.table;          // undefined while none is chosen
  // table = { name: 'sizes.csv', ext: 'csv', size: 1234, bytes: Uint8Array, text: '…' }
  const rows = table ? table.text.split('\n').map((line) => line.split(',')) : [];
  …
}
```

`text` is there for a text file (`txt`, `csv`, `tsv`, `json`, `svg`, `xml`, `obj`, `dxf`, `md`, `gcode`) that reads
as UTF-8; `bytes` always. A file is at most 4 MB. What the plugin makes from it is geometry, so the model does not
need the file again. Data that belongs to the plugin itself (a table of standard sizes) is best written into the
plugin as a constant.

### The user's language

`wazo.language` is the language the user reads WAZO3D in (`'en'`, `'nl'`, `'de'` or `'es'`), for words a plugin writes
into its model, such as names engraved on a part. What it says in its dialog it gives in every language itself.

### Steps (`build`)

`build(values)` returns a list of steps (or `{ steps, figures, notes }`, above). It may be `async`. WAZO3D carries them out in order, each on what
the steps before it built, and the result must be **one closed solid**. Everything is in millimetres:

- y is up;
- the front faces +z;
- an outline is a list of points `[x, y]` in the plane z = 0, in either direction (the last point need not
  repeat the first).

| step | what it does |
|---|---|
| `{ op: 'extrude', outline, depth, holes?, z?, part? }` | Pushes the outline back from `z` (0 by default) to `z − depth`. `holes` are outlines inside it. |
| `{ op: 'revolve', profile, segments?, part? }` | Turns a profile of points `[r, z]` (r ≥ 0, the distance from the z axis) once round the z axis, in `segments` steps (48 by default). A point on the axis closes the solid there. |
| `{ op: 'cut', outline, depth?, part? }` | Cuts a hole along the outline right through, or `depth` back from the front. |
| `{ op: 'collar', outline, width, depth }` | A band round an opening, standing out of the front. |
| `{ op: 'coping', height, overhang?, y? }` | A slab along the top. |
| `{ op: 'group', steps, at?, turn?, tilt?, roll? }` | The steps in a frame of their own: rolled `roll`° about x, tilted `tilt`° about z, turned `turn`° about y, moved to `at` `[x, y, z]`. |
| `{ op: 'repeat', steps, count, every }` | The steps `count` times, each copy `every` `[dx, dy, dz]` further on. |
| `{ op: 'move', pick, at?, tilt?, turn?, roll? }` | After every other step, the closed piece nearest `pick` `[x, y, z]` (as built) rolled `roll`° about x (+90 takes y to z), tilted `tilt`° about z, turned `turn`° about y and moved by `at`: lay a kit's parts out apart on the print bed (27 Oct 2026). Only at the top; two moves cannot take one piece. |
| `{ op: 'arrange', gap?, width?, depth? }` | After the moves, every closed piece laid out on the ground beside the others, keeping its height: along x or turned a quarter about y, in rows no longer than `width` mm, `gap` mm apart, the rows within `depth` where they can be, in the least area (28 Oct 2026). Only at the top. |
| `{ op: 'paint', part, colour: '#rrggbb' }` | Paints a part with a colour. |
| `{ op: 'paint', part, texture: { series: 'realistic', id } }` | Paints a part with one of WAZO3D's standard textures. |
| `{ op: 'decal', texture: { series, id }, at: [x, y, z], normal: [x, y, z], size?: [w, h], turn?, optional? }` | Lays a standard texture once on the face through `at` looking along `normal`, `size` mm (the texture's own when left out), cut into the face; its height map carves into a print (25 Oct 2026). |

How the steps combine:

- `extrude` and `revolve` are added to what is there.
- `cut` takes away from it.
- Give a solid step a `part` name (`'tyre'`, `'hub'`) so a later paint step can find it. `'all'` paints
  everything.

When the settings ask for something the plugin cannot make, return `{ refused: { en, nl, de, es } }` rather than throwing:
the user reads why in their own language, in a box that stands out, and the last model that could be made stays in the
view. Say what would work (*the crank throw must be between 120 and 380 mm*), not only that it does not.

A step that cannot be built (an outline crossing itself, a depth of nought, …) stops the plugin with a message
naming the step. To stop it yourself with words of your own, `throw new Error('…')` in `build`. The message is
shown to the user.

**Limits:** 500 steps, 200 000 points, and five seconds for `build`.

### Choices that depend on what was made (`offers`)

`build` may return `offers`: per `choice` setting, the values of its own options that make sense for what was made, as
`{ plate: ['1', '2', '3'] }`. The dialog then offers only those, and hides the setting when there is one or none; a value
no longer offered moves to the last one offered and the model is made again. The soldering jig offers as many print beds
as the jig takes.

### Anchors (`anchors`)

A model another system hangs something on can say where: `{ steps, anchors: [{ kind, cantilever?, at, toward? }] }`.
`kind` is a short word in small letters (the catenary mast uses `contact`, `messenger` and `pull`), `cantilever` counts
from 0 (a mast with two cantilevers, a portal's tracks), `at` is the point in millimetres in the model's own frame, and
`toward` an optional direction (for `pull`, the way the wire runs off to its span). The .glb a host gets carries each
as an empty node named `wazo:anchor:<kind>:<cantilever>` at the point, in metres, with
`extras.wazo = { anchor, cantilever, toward? }`, so the host reads the points off the file instead of copying the
model's geometry. At most 64; a kind and cantilever only once.

## What a plugin can and cannot do

A plugin runs **sealed off**:

- in a sandboxed frame with no origin of its own;
- under a policy that lets nothing in or out;
- in a worker of its own inside that frame.

It cannot see the page, the model, the account or anything stored in the browser. It cannot reach the network
or load other code. What it hands back must be plain data (numbers, strings, lists, objects). A plugin that runs
longer than five seconds is stopped.

So a plugin is plain arithmetic: settings in, steps out. That is also why the steps are checked once more
before anything is built. The most a plugin can do is hand over a shape, or fail.

## Sharing

The examples are MIT licensed. Copy them, change them, share them. Give your plugin an `id` of its own (your
name or site first) so it does not replace someone else's.
