Commands, Menus and Dialogs
An extension package can add to ParametriQ CAD's window: commands that run with the drawing API and are undone as one step, menu items and toolbar buttons that run them, forms and picks in the drawing they ask for, and a stripped-down workspace with some of the app's own items hidden. The package only describes these; the app makes the controls, in its own style, so a package keeps working when the app's look changes.
A package
A package is a folder in your Extensions folder (File › Extensions › Open Extensions Folder), or in the app's, with
a manifest.json:
QuickBox/
manifest.json
main.js what it declares
strings/en.json its text in English (es.json, zh-Hans.json for Spanish and Chinese)
icons/quickbox.svg its icons
{
"id": "quickBox",
"name": "Quick box",
"version": "1.0",
"main": "main.js",
"description": "A regular slotted box from three sizes.",
"permissions": ["commands", "menus", "dialogs", "pick", "drawing", "designs"],
"enabled": true
}
| Property | Meaning |
|---|---|
id |
Its id: letters, digits, _ and -. Start its commands' ids with it (quickBox.box). A package of yours with the same id as one of the app's takes its place. |
name, version, description, author |
Shown in File › Extensions › Manage Extensions…. |
main |
The script that declares what it adds (main.js if left out). |
permissions |
What it may do (below). Anything else it tries is refused, with a message naming the permission it needs. |
enabled |
false: it is off until turned on in Manage Extensions… (for a special setup, such as an operator's workspace). |
A lone .js file in the Extensions folder can declare post-processors, design rules, templates, reports and save
extensions, but not commands, menus or dialogs: those need a package.
Permissions
| Permission | Lets it |
|---|---|
commands |
Declare commands with command({…}). |
menus |
Add menu items, submenus, toolbar buttons and toolbar groups. |
customise |
Hide and move the app's own menu items, toolbar buttons and groups. |
dialogs |
Ask with ui.form, ui.message, ui.confirm, and show ui.progress. |
pick |
Ask the user to pick in the drawing with ui.pick. |
drawing |
Change the drawing from a command (without it, a command can only read it). |
designs |
Open the library's parametric designs as new drawings with ui.runDesign. |
Turning packages on and off
File › Extensions › Manage Extensions… lists the packages, with what each uses; turn each on or off and choose
Apply: the extensions are read again and the window changes at once. Your choice is kept in packages.json in your
Extensions folder.
Its strings
t("key") gives the package's text for a key in the language the app is shown in: from strings/es.json for
Spanish, strings/zh-Hans.json for Chinese, and strings/en.json for English, or for a key the language's table
doesn't have. t("count", n) fills {0} (and {1} …) in the text from what follows the key.
{ "boxTitle": "_Quick Box…", "pickCentre": "Click the middle of the hand hole" }
Icons
An icon is an SVG file in the package's folder, named from it ("icons/quickbox.svg"), or one of the app's own by its
name ("tuckflaptool"). Draw it in a square viewBox with currentColor for its lines: the app colours them for the
light or dark theme, like its own icons.
Commands
command({
id: "quickBox.handHole",
title: t("handHoleTitle"), // "_Hand Hole…"
description: t("handHoleDescription"),
icon: "icons/handhole.svg",
shortcut: "Ctrl+Shift+H",
industries: ["Packaging"],
enabledWhen: "drawing",
async run(state) {
const centre = await ui.pick({ kind: "point", prompt: t("pickCentre") });
if (!centre) return; // Esc
circle(centre, 15);
}
});
| Property | Meaning |
|---|---|
id |
Its id, unique among the commands installed: what menu items and buttons name it by. |
title |
Its label; _ before its access key. |
description |
Shown as its tip. |
icon |
Its icon. |
shortcut |
A keyboard shortcut (Ctrl+Shift+B). One the app or another command already uses is left out, and said when extensions are read. |
industries |
The industries it is offered for (left out: every one). Its menu items and buttons are shown only when the drawing is for one of them (or General, or Show All Tools is on). |
enabledWhen |
"drawing" (a drawing is open: the default), "selection" (something is selected), "always", or a function given the state below, returning true when it can run. |
run(state) |
What it does. state is {hasDrawing, industry, selected} (selected: the ids of the elements selected). |
run works on the drawing worked on with the drawing API (line, newPoint, setVariable,
command …), as a template does. Everything it changes is undone as one step (Edit › Undo names the
command). If it goes wrong (an error it doesn't catch), what it changed is taken back, and the message says why, with
the file and line.
Commands are run from the menu items and buttons that name them, by their shortcut, and from Run Command….
Run Command…
File › Extensions › Run Command… (Ctrl+Shift+P) opens the command palette (View › Command Palette…, Ctrl+K), which finds any command by typing part of its name: the extensions' commands, and every item of the menus that can be used now (shown with the menus it is in: File › Export), the tools, the settings, recent drawings and help pages. Each word typed must be in the name or its menus; the arrow keys choose, and Enter runs it.
Menus and toolbar
uiExtension({…}) describes what a package adds to the menus and the toolbar, and what of the app's own it hides or
moves:
uiExtension({
name: "Quick box menus and toolbar",
menus: [
{ menu: "Tool/Packaging", command: "quickBox.box" },
{ menu: "File/Export", command: "quickBox.export" },
{ menu: "Extensions", submenu: "quickBox", title: "_Quick Box" },
{ menu: "Extensions/quickBox", command: "quickBox.handHole" },
{ menu: "File", separator: true, after: "ExportForPalletSoftware" }
],
toolbar: [
{ group: "Packaging", command: "quickBox.box", after: "CreaseOffset" },
{ group: "quickBox", title: "Quick box", after: "Packaging", industries: ["Packaging"],
commands: ["quickBox.box", "quickBox.handHole"] }
]
});
Menu items
| Property | Meaning |
|---|---|
menu |
Where it goes: a menu's path (File, Extensions, Tool/Packaging). A path ending at an item puts it after that item (File/Export); a path ending at a heading, at the end of that heading's section (Tool/Packaging: the Packaging tools in Tool › Packaging Tools). |
command |
The command it runs. |
submenu, title |
Instead of a command: a submenu, its id and its label. Later items go into it by its path (Extensions/quickBox). |
separator |
true: a line instead of an item. |
before, after |
The id of the item (in the same menu) it goes before or after. |
title |
Its label, if not its command's. |
industries |
Shown only for these industries (left out: its command's; in an industry's section, that industry). |
Toolbar buttons and groups
| Property | Meaning |
|---|---|
group |
The id of the group the buttons go in: one of the app's (below), or a new one. |
command, commands |
The command, or commands, whose buttons are added, in order. |
title |
A new group: its name (shown as its tip). |
before, after |
A button's id in the group it goes before or after; for a new group, a group's id. A new group goes at the end of the toolbar if neither is given. |
industries |
Shown only for these industries (a new group; else its commands'). |
Ids
Menus, items, buttons and groups are named by ids that are the same whatever language the app is shown in, and
written in paths with /: File/Export, Draw/Bezier. A path whose first step isn't a menu of the menu bar is looked
for anywhere (Bezier: every Bezier button; Tool/Packaging: the heading inside Tool's submenu). Extensions is
File › Extensions.
| What | Its id | Examples |
|---|---|---|
| The menu bar's menus | Their names in English | File, Edit, Production, 3D, View, Help (Tool is in Edit: Tool, or Edit/Tool) |
| Menu items and submenus | Their English names, without spaces or punctuation | File/NewFromTemplate, File/Export, File/SendToMachine, File/Extensions, Edit/Constrain, Edit/CheckParametric, Tool/Line |
| The submenus a menu's items are grouped in | Their English names; File's and 3D's Import and Export ImportMenu and ExportMenu, Tool's Layout ToolLayout (the items have the plain names). A path to an item in one finds it there (File/Export, Tool/Line) |
Tool/Draw, Tool/Modify, Tool/Dimensions, Tool/Insert, Tool/MeasureAndInspect, Tool/ToolLayout, File/ImportMenu, File/ExportMenu, 3D/Display, 3D/Folding, 3D/MeasureAndAnnotate, 3D/ModelsAndProducts, 3D/ExportMenu |
| Tool › Industry Tools | IndustryTools; its headings by industry |
Tool/IndustryTools, Tool/Production, Tool/Packaging, Tool/Cutting, Tool/SheetMetal |
| A tool's item or button | The tool's name | Line, Fillet, TuckFlap, FlapOrSeam, RegistrationMark |
| Toolbar groups | What they hold | Select, Draw, Modify, Annotate, Insert, Inquiry; in the industry tools (IndustryTools): Production, Cutting and each industry (Packaging, SheetMetal …) |
A shared tool's button (FlapOrSeam, Holes, BridgeOrTab …: see Tools) is in each of its industries'
groups: Packaging/FlapOrSeam is the one in the Packaging group, FlapOrSeam all of them.
Hiding and moving the app's own
A package with the customise permission can hide menu items, whole menus, toolbar buttons and groups, and move
them: for an operator who only opens, checks and sends drawings to the machine, say.
uiExtension({
name: "Operator workspace",
hide: {
menus: ["File/NewFromTemplate", "File/SendByEmail", "Edit/Constrain", "3D"],
toolbar: ["Draw/Bezier", "Draw/Spline", "Annotate"]
},
move: {
menus: [{ item: "File/SendToMachine", after: "Save" }],
toolbar: [{ item: "Inquiry", before: "Draw" }]
}
});
A move names the item and where it goes: before or after an id in the same menu or group, or a path anywhere. A
line left at the start or end of a menu, or next to another, by what is hidden is taken away. Shortcuts still work
for what is hidden. The app comes with this package (Operator workspace), turned off.
Access keys
A menu item's access key is the letter after the _ in its title. If another item in the same menu already uses it
(for the industries it is shown for: in Tool › Packaging Tools, the items a packaging drawing shows), the app gives
it the first free letter that starts a word of its title, else any free letter in it; if every letter is taken, it
keeps its own, shared. Reload Extensions says what it changed.
What goes wrong
An id or menu that isn't there, a command no package declares, or a shortcut already used is said when the extensions are read (as for an extension file that can't be read), with the package's name; the rest of what it adds is still added.
Dialogs and picks
A command's run can ask things with the functions of ui. Each gives back a promise: run is an async
function that awaits it, and the app goes on working (drawing, moving the view) while the user answers. Each piece of
the command between two awaits may run for ten seconds and a million statements, as any extension call may.
| Function | Gives | Permission |
|---|---|---|
ui.form({ title, fields, … }) |
The values by field id, or null if cancelled. |
dialogs |
ui.pick({ kind, prompt, filter }) |
What was picked, or null if cancelled (Esc). |
pick |
ui.message(text, { title, kind }) |
(When closed.) kind: "information", "warning" or "error". |
dialogs |
ui.confirm(text, { title }) |
true for Yes. |
dialogs |
ui.progress({ title, cancellable }) |
A progress window: await p.update(fraction, text) shows how far it has got and gives false once Cancel is chosen; p.cancelled(); p.close() (closed anyway when the command ends). |
dialogs |
ui.runDesign(name, values) |
Opens a library design as a new drawing with the values given, as File › Run Parametric does: true if it could. name: "Packaging/FEFCO 0201 Regular slotted container", or its own name if only one design has it; a choice by its text. |
designs |
They can only be used while a command runs.
Forms
const sizes = await ui.form({
title: "Quick box",
description: "The box's inside sizes.",
ok: "Make Box",
fields: [
{ id: "L", label: "Length", type: "length", default: 300, min: 10, max: 2000 },
{ id: "W", label: "Width", type: "length", default: 200, min: 10 },
{ id: "board", label: "Board", type: "choice", choices: ["E flute", "B flute"], default: "B flute" },
{ id: "lid", label: "Lid", type: "check", default: true },
{ id: "at", label: "Corner", type: "point", default: { x: 0, y: 0 } }
],
validate: v => v.W > v.L ? "The width is longer than the length." : null
});
if (!sizes) return;
The form is shown as the app's options dialogs are, one row a field.
| Field property | Meaning |
|---|---|
id |
The name its value is given back by. |
label, description |
What its row says, and under it. |
type |
"number", "integer", "length" (in the drawing's units), "angle" (degrees), "text", "choice", "check" (yes or no), or "point" ({x, y}). Left out: from default (or "choice" if there are choices). |
default |
Where it starts. |
choices |
A choice's choices. |
min, max |
A number's limits. |
unit |
Shown after a number. |
required |
true: text can't be left empty. |
Before the form closes, each value is checked against its field (a number within its limits, a whole number, a choice
from its list, text given), then by validate(values) if the form has one: a message (or false) says what is wrong,
and the form is shown again with it. Values are remembered for the next time the form is shown (remember: false: it
starts from the defaults each time).
Picks
const at = await ui.pick({ kind: "point", prompt: "Click the middle of the hole" });
const edge = await ui.pick({ kind: "element", filter: { kinds: ["LINE", "ARC"], cutting: true } });
const outlines = await ui.pick({ kind: "elements", filter: { closed: true } });
| Kind | The user | Gives |
|---|---|---|
"point" |
Clicks a place. | {x, y}; and id if they clicked on a point, so what is made on it follows it. |
"element" |
Clicks an element. | The element, as drawingInfo() describes it (id, kind, layer, length …). |
"elements" |
Clicks elements (again: left out), then right-clicks. | A list of them. |
filter limits the elements that are lit and can be picked: kinds ("LINE", "ARC", "CIRCLE" …), layers (their
numbers), closed and cutting (true or false). The prompt is shown in the Interactive window and the status
bar.
How waiting works
The engine runs JavaScript's promises and async functions itself. When run awaits a ui function, the command
stops there and the app shows the dialog; the app's window goes on working. When the user answers, the command goes on
from the await with the answer, on the app's own thread, so it never runs at the same time as anything else in the
drawing. Only one command runs at a time: until it ends, the others can't be chosen. A command that never awaits runs
straight through, as any extension call does.
An example: Quick box
The app comes with the Quick box package (in its Extensions folder): Quick Box… asks for a box's length, width,
depth and board and opens the library's FEFCO 0201 design with them; Hand Hole… asks for a point and a size and
draws a rounded slot there on the cut layer, its width and height variables (HH1W, HH1H) so it stays parametric.
Both are in Tool › Packaging Tools and in a toolbar group of their own after the Packaging group, shown for
packaging drawings. Copy its folder to yours, give it a new id, and change it to make your own.
See also: Extensions, Drawing API, Templates, Industries.