@field/cascade 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +20 -3
- package/CHANGELOG.md +187 -0
- package/README.md +1 -2
- package/dist/assets/{CodeEditor-DSZBpzdy.js → CodeEditor-4O7FQ__R.js} +4 -4
- package/dist/assets/{CodePanel-sY6sZa7B.js → CodePanel-BU-autmq.js} +2 -2
- package/dist/assets/{CustomNodeDialog-BRKnqZhp.js → CustomNodeDialog-BRpDfOV5.js} +1 -1
- package/dist/assets/{OpenGraphDialog-ZsgL-iwr.js → OpenGraphDialog-BwbRlC0X.js} +1 -1
- package/dist/assets/{ProjectSettings-Cw17nNe3.js → ProjectSettings-DLOXLxcA.js} +1 -1
- package/dist/assets/{SettingsDialog-p25h3tj0.js → SettingsDialog-Bz8JxISH.js} +1 -1
- package/dist/assets/{VersionHistoryDialog-DzFLUotB.js → VersionHistoryDialog-D5Z0nJ5-.js} +1 -1
- package/dist/assets/{core-BICrEt0y.js → core-Yqi0Ni61.js} +1 -1
- package/dist/assets/{cssMode-D7TXw57I.js → cssMode-CxUzc4vp.js} +1 -1
- package/dist/assets/{definitions-DUww2Rt-.js → definitions-GreEMXLV.js} +1 -1
- package/dist/assets/{editor.api-DvkCO3ko.js → editor.api-BNFb25CX.js} +1 -1
- package/dist/assets/{freemarker2-BA2lxsF1.js → freemarker2-0ZDnWxeM.js} +1 -1
- package/dist/assets/geo-u0n_jOsD.js +1 -0
- package/dist/assets/{handlebars-BcDiyPd8.js → handlebars-Cy18MoaD.js} +1 -1
- package/dist/assets/{html-C7O4TyhX.js → html-B-2Dx7nq.js} +1 -1
- package/dist/assets/{htmlMode-WaDGF3Mj.js → htmlMode-DUAc9B_d.js} +1 -1
- package/dist/assets/{image-DR4oeyJB.js → image-cAa-Nu9O.js} +1 -1
- package/dist/assets/{index-DyvDC6pS.js → index-5ZtDAuy2.js} +42 -41
- package/dist/assets/{javascript-CpIEK7tz.js → javascript-DeQsDqP5.js} +1 -1
- package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-BiFwJVSk.js} +1 -1
- package/dist/assets/{liquid-CN6FFHZE.js → liquid-BLoHH68a.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-CNFgm1Kv.js} +1 -1
- package/dist/assets/math-DioIQOPJ.js +1 -0
- package/dist/assets/{mdx-DE89pafV.js → mdx-CFXQVNfs.js} +1 -1
- package/dist/assets/pop-DC_TQG9t.js +1 -0
- package/dist/assets/{python-ByTllTB9.js → python-Cf7S7p2E.js} +1 -1
- package/dist/assets/{razor-USYlRRCE.js → razor-CMHBcG-g.js} +1 -1
- package/dist/assets/{tsMode-BXpN8iUz.js → tsMode-BEih0GDj.js} +1 -1
- package/dist/assets/{typescript-DInBjGQQ.js → typescript-COEKYD_a.js} +1 -1
- package/dist/assets/{workers-C7Ye5B7D.js → workers-BNhAhMNn.js} +1 -1
- package/dist/assets/{xml-U0Uocqyo.js → xml-CPiMpnPl.js} +1 -1
- package/dist/assets/{yaml-CBw_PVYf.js → yaml-Bgp1ITLR.js} +1 -1
- package/dist/cli/Graph-ZJJTRQTM.js +2975 -0
- package/dist/cli/chunk-2KXB47FP.js +115 -0
- package/dist/cli/chunk-35D343DC.js +1447 -0
- package/dist/cli/chunk-36P5K3SD.js +631 -0
- package/dist/cli/chunk-3JTJHF7V.js +267 -0
- package/dist/cli/chunk-4UDFYF5G.js +132 -0
- package/dist/cli/chunk-53NMBJAO.js +35 -0
- package/dist/cli/chunk-A4OAMOZH.js +310 -0
- package/dist/cli/chunk-BIJO5OFS.js +71 -0
- package/dist/cli/chunk-BP7UKF36.js +376 -0
- package/dist/cli/chunk-CKS2FQWR.js +89 -0
- package/dist/cli/chunk-CO22XHNG.js +593 -0
- package/dist/cli/chunk-D6BUOHUG.js +634 -0
- package/dist/cli/chunk-DEQSEFFH.js +153 -0
- package/dist/cli/chunk-DUIUFVU2.js +241 -0
- package/dist/cli/chunk-EUIAC3U3.js +3006 -0
- package/dist/cli/chunk-FCDXJ6EK.js +2208 -0
- package/dist/cli/chunk-GFS6W3C5.js +308 -0
- package/dist/cli/chunk-HFHWYL55.js +139 -0
- package/dist/cli/chunk-HSNOBIVT.js +263 -0
- package/dist/cli/chunk-ICH7N5PN.js +26 -0
- package/dist/cli/chunk-J7VK7MNF.js +418 -0
- package/dist/cli/chunk-KH4MUBKF.js +1551 -0
- package/dist/cli/chunk-MIIEFUGG.js +623 -0
- package/dist/cli/chunk-OWWBYLHC.js +136 -0
- package/dist/cli/chunk-POMMNKZZ.js +672 -0
- package/dist/cli/chunk-QBBKRNGY.js +90 -0
- package/dist/cli/chunk-QQPA2ZHH.js +273 -0
- package/dist/cli/chunk-RLCAJBVR.js +224 -0
- package/dist/cli/chunk-RQZ4SX5R.js +1125 -0
- package/dist/cli/chunk-RUU3YWXU.js +99 -0
- package/dist/cli/chunk-RXDIMXN5.js +98 -0
- package/dist/cli/chunk-SGJAET2L.js +197 -0
- package/dist/cli/chunk-VVGSLVSF.js +151 -0
- package/dist/cli/chunk-WD62JIP6.js +118 -0
- package/dist/cli/chunk-WIH4TEIS.js +104 -0
- package/dist/cli/chunk-WKURDVYP.js +1119 -0
- package/dist/cli/chunk-WPUDGEFY.js +20 -0
- package/dist/cli/chunk-XWDSKG7N.js +1301 -0
- package/dist/cli/chunk-YAT5Y25U.js +121 -0
- package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
- package/dist/cli/cliCommands-UQSFGEHF.js +3464 -0
- package/dist/cli/commands-Q23M26QM.js +1292 -0
- package/dist/cli/definitions-HF2FTHYU.js +32 -0
- package/dist/cli/doctorCommand-B7BPQMN6.js +76 -0
- package/dist/cli/edit-JHCPTQFT.js +28 -0
- package/dist/cli/editorCommands-UIF5YLUZ.js +33 -0
- package/dist/cli/encode-HAUZTYPT.js +17 -0
- package/dist/cli/frames-LSAUBZ7P.js +16 -0
- package/dist/cli/geo-4RNK3QVN.js +43 -0
- package/dist/cli/graphCommands-6BMAKGQE.js +184 -0
- package/dist/cli/graphFile-VPSQ6OF7.js +286 -0
- package/dist/cli/headlessCanvas-5XLDZT7M.js +15 -0
- package/dist/cli/helpCommand-3VRE3KVO.js +155 -0
- package/dist/cli/index.js +100 -30756
- package/dist/cli/math-ZRMLKU2D.js +16 -0
- package/dist/cli/mcp-ZLEEMYGF.js +164 -0
- package/dist/cli/parity-KINARCP2.js +328 -0
- package/dist/cli/pop-USDFXB7H.js +39 -0
- package/dist/cli/project-K3QIPGBB.js +12 -0
- package/dist/cli/projectDefinition-DMTZN2NE.js +27 -0
- package/dist/cli/projectRuntime-Z5PJOH47.js +48 -0
- package/dist/cli/registerStandardNodes-S2VLMF6J.js +3081 -0
- package/dist/cli/render-YURAPPUI.js +436 -0
- package/dist/cli/runCommand-PI764LEG.js +198 -0
- package/dist/cli/runner-RA6UKEPD.js +37 -0
- package/dist/cli/runtime/image.ts +277 -0
- package/dist/cli/runtime/stage.ts +4 -1
- package/dist/cli/stageRunner-KUNIMQKJ.js +10 -0
- package/dist/cli/verbs-RCE3K4VQ.js +19 -0
- package/dist/cli/webBuild-GT4PZT5D.js +37 -0
- package/dist/contracts/documentFormat.d.ts +18 -0
- package/dist/contracts/documentFormat.js +195 -0
- package/dist/contracts/editor.d.ts +17 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/image.d.ts +141 -0
- package/dist/image.js +127 -0
- package/dist/index.html +1 -1
- package/dist/player/editor.js +32 -7
- package/dist/runtime/builtins/headless.d.ts +19 -0
- package/dist/runtime/builtins/headless.js +25 -0
- package/dist/runtime/builtins/image/contactSheet.d.ts +226 -0
- package/dist/runtime/builtins/image/contactSheet.js +199 -0
- package/dist/runtime/builtins/image/index.d.ts +9 -0
- package/dist/runtime/builtins/image/index.js +9 -0
- package/dist/stage.js +4 -1
- package/doc/CLI_REFERENCE.md +73 -2
- package/doc/EDITOR_EXPORT.md +11 -2
- package/doc/MCP.md +17 -1
- package/doc/NODE_REFERENCE.md +142 -0
- package/doc/PROJECT_AUTHORING.md +127 -7
- package/package.json +16 -4
- package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
- package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
- package/dist/assets/geo-C2RHOVHC.js +0 -1
- package/dist/assets/math-DaDEgWKt.js +0 -1
- package/dist/assets/pop-pLH7L0PW.js +0 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cascade.image.ContactSheet` — several images as one labelled grid.
|
|
3
|
+
*
|
|
4
|
+
* The selection loop is a ladder of three to five options and a pick, and the
|
|
5
|
+
* pick is made by looking at them side by side. So the sheet is a node rather
|
|
6
|
+
* than a script beside the graph: a graph can end in one, and `cascade ladder`
|
|
7
|
+
* lays out its variants with the same layout and drawing code below.
|
|
8
|
+
*
|
|
9
|
+
* `portable` with `assets`, like `cascade.geo.Render`: it reads each image
|
|
10
|
+
* through the asset capability, decodes it with `createImageBitmap` and draws
|
|
11
|
+
* on an `OffscreenCanvas`, which both hosts supply (the headless one through
|
|
12
|
+
* @napi-rs/canvas). It lives in `cascade.image.*` beside Studio's image
|
|
13
|
+
* library, as the first definition-v1 node there; Studio does not register it
|
|
14
|
+
* yet, so today it runs headless and is listed in `../headless.ts` rather
|
|
15
|
+
* than in the built-ins Studio bundles.
|
|
16
|
+
*/
|
|
17
|
+
export const contactSheetDefinition = {
|
|
18
|
+
apiVersion: 1,
|
|
19
|
+
label: "Contact Sheet",
|
|
20
|
+
description: "Lay images out as one labelled grid.",
|
|
21
|
+
icon: "LayoutGrid",
|
|
22
|
+
runsOn: "portable",
|
|
23
|
+
capabilities: ["assets"],
|
|
24
|
+
inputs: {
|
|
25
|
+
images: { kind: "data", type: "image", variadic: true },
|
|
26
|
+
/** More images after the wired ones: image values or project-relative paths. */
|
|
27
|
+
list: { kind: "data", type: "array" },
|
|
28
|
+
},
|
|
29
|
+
outputs: {
|
|
30
|
+
image: { kind: "data", type: "image" },
|
|
31
|
+
asset: { kind: "data", type: "asset" },
|
|
32
|
+
},
|
|
33
|
+
props: {
|
|
34
|
+
columns: { type: "int", default: 0, min: 0, max: 64, description: "0: as square as the count allows." },
|
|
35
|
+
gap: { type: "int", default: 16, min: 0, max: 512 },
|
|
36
|
+
background: { type: "color", default: [1, 1, 1, 1], label: "Background" },
|
|
37
|
+
cellWidth: { type: "int", default: 0, min: 0, max: 8192, description: "0: images at their own size. Otherwise each is scaled to fit this width." },
|
|
38
|
+
labels: { type: "string", default: "", description: "One label per line, in image order." },
|
|
39
|
+
labelSize: { type: "int", default: 14, min: 6, max: 128 },
|
|
40
|
+
labelColor: { type: "color", default: [0.1, 0.1, 0.1, 1], label: "Label Colour" },
|
|
41
|
+
filename: { type: "string", default: "contact-sheet.png", label: "Filename" },
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Where each image goes. By default every cell is as wide as the widest image
|
|
46
|
+
* and as tall as the tallest, and images keep their size. With `cellWidth`,
|
|
47
|
+
* cells are that wide and as tall as the tallest image needs at that width,
|
|
48
|
+
* and each image is scaled to fit. Either way an image is centred in its cell
|
|
49
|
+
* and never cropped, in whole pixels, so a sheet is the same bytes every run.
|
|
50
|
+
*/
|
|
51
|
+
export function contactSheetLayout(sizes, options = {}) {
|
|
52
|
+
const count = sizes.length;
|
|
53
|
+
if (!count)
|
|
54
|
+
throw new Error("cascade.image.ContactSheet needs at least one image");
|
|
55
|
+
for (const [width, height] of sizes) {
|
|
56
|
+
if (!(width > 0 && height > 0))
|
|
57
|
+
throw new Error(`cascade.image.ContactSheet: an image is ${width}x${height}`);
|
|
58
|
+
}
|
|
59
|
+
const gap = Math.max(0, Math.round(options.gap ?? 16));
|
|
60
|
+
const columns = Math.min(count, options.columns && options.columns > 0 ? Math.round(options.columns) : Math.ceil(Math.sqrt(count)));
|
|
61
|
+
const rows = Math.ceil(count / columns);
|
|
62
|
+
const scaled = Boolean(options.cellWidth && options.cellWidth > 0);
|
|
63
|
+
const cellWidth = scaled ? Math.round(options.cellWidth) : Math.max(...sizes.map(([width]) => width));
|
|
64
|
+
const cellHeight = scaled
|
|
65
|
+
? Math.max(...sizes.map(([width, height]) => Math.round((height * cellWidth) / width)))
|
|
66
|
+
: Math.max(...sizes.map(([, height]) => height));
|
|
67
|
+
const labelHeight = Math.max(0, Math.round(options.labelHeight ?? 0));
|
|
68
|
+
const cells = sizes.map(([width, height], index) => {
|
|
69
|
+
const column = index % columns;
|
|
70
|
+
const row = Math.floor(index / columns);
|
|
71
|
+
// Native size unless a cell width was asked for; never cropped.
|
|
72
|
+
const fit = Math.min(cellWidth / width, cellHeight / height);
|
|
73
|
+
const scale = scaled ? fit : Math.min(1, fit);
|
|
74
|
+
const drawnWidth = Math.max(1, Math.round(width * scale));
|
|
75
|
+
const drawnHeight = Math.max(1, Math.round(height * scale));
|
|
76
|
+
const left = gap + column * (cellWidth + gap);
|
|
77
|
+
const top = gap + row * (cellHeight + labelHeight + gap);
|
|
78
|
+
return {
|
|
79
|
+
x: left + Math.floor((cellWidth - drawnWidth) / 2),
|
|
80
|
+
y: top + Math.floor((cellHeight - drawnHeight) / 2),
|
|
81
|
+
width: drawnWidth,
|
|
82
|
+
height: drawnHeight,
|
|
83
|
+
labelX: left,
|
|
84
|
+
labelY: top + cellHeight,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
width: gap + columns * (cellWidth + gap),
|
|
89
|
+
height: gap + rows * (cellHeight + labelHeight + gap),
|
|
90
|
+
columns,
|
|
91
|
+
rows,
|
|
92
|
+
cell: [cellWidth, cellHeight],
|
|
93
|
+
labelHeight,
|
|
94
|
+
cells,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** The host's canvas, read here and nowhere else (see `cascade.geo.Render`). */
|
|
98
|
+
function host() {
|
|
99
|
+
const globals = globalThis;
|
|
100
|
+
const Canvas = globals.OffscreenCanvas;
|
|
101
|
+
const createBitmap = globals.createImageBitmap;
|
|
102
|
+
if (!Canvas || !createBitmap || !globals.Blob) {
|
|
103
|
+
throw new Error("cascade.image.ContactSheet needs OffscreenCanvas and createImageBitmap, which this host does not provide");
|
|
104
|
+
}
|
|
105
|
+
const BlobType = globals.Blob;
|
|
106
|
+
return {
|
|
107
|
+
surface: (width, height) => new Canvas(width, height),
|
|
108
|
+
decode: (bytes) => createBitmap(new BlobType([bytes])),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function css(colour, fallback) {
|
|
112
|
+
const [r = 0, g = 0, b = 0, a = 1] = colour ?? fallback;
|
|
113
|
+
return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`;
|
|
114
|
+
}
|
|
115
|
+
/** Encoded images (PNG, JPEG, WebP) in, one PNG sheet out. */
|
|
116
|
+
export async function composeContactSheet(images, options = {}) {
|
|
117
|
+
const canvas = host();
|
|
118
|
+
const bitmaps = [];
|
|
119
|
+
try {
|
|
120
|
+
for (const bytes of images)
|
|
121
|
+
bitmaps.push(await canvas.decode(bytes));
|
|
122
|
+
const labels = options.labels ?? [];
|
|
123
|
+
const labelSize = Math.round(options.labelSize ?? 14);
|
|
124
|
+
const labelled = labels.some((label) => label.trim().length > 0);
|
|
125
|
+
const layout = contactSheetLayout(bitmaps.map((bitmap) => [bitmap.width, bitmap.height]), {
|
|
126
|
+
columns: options.columns,
|
|
127
|
+
gap: options.gap,
|
|
128
|
+
cellWidth: options.cellWidth,
|
|
129
|
+
labelHeight: labelled ? Math.round(labelSize * 1.8) : 0,
|
|
130
|
+
});
|
|
131
|
+
const surface = canvas.surface(layout.width, layout.height);
|
|
132
|
+
const context = surface.getContext("2d");
|
|
133
|
+
if (!context)
|
|
134
|
+
throw new Error("cascade.image.ContactSheet: no 2D context");
|
|
135
|
+
context.fillStyle = css(options.background, [1, 1, 1, 1]);
|
|
136
|
+
context.fillRect(0, 0, layout.width, layout.height);
|
|
137
|
+
context.imageSmoothingEnabled = true;
|
|
138
|
+
context.imageSmoothingQuality = "high";
|
|
139
|
+
layout.cells.forEach((cell, index) => context.drawImage(bitmaps[index], cell.x, cell.y, cell.width, cell.height));
|
|
140
|
+
if (labelled) {
|
|
141
|
+
context.fillStyle = css(options.labelColor, [0.1, 0.1, 0.1, 1]);
|
|
142
|
+
context.font = `${labelSize}px sans-serif`;
|
|
143
|
+
context.textBaseline = "top";
|
|
144
|
+
layout.cells.forEach((cell, index) => {
|
|
145
|
+
const label = labels[index];
|
|
146
|
+
if (label)
|
|
147
|
+
context.fillText(label, cell.labelX, cell.labelY + Math.round((layout.labelHeight - labelSize) / 2), layout.cell[0]);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const blob = await surface.convertToBlob({ type: "image/png" });
|
|
151
|
+
return { bytes: new Uint8Array(await blob.arrayBuffer()), layout };
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
for (const bitmap of bitmaps)
|
|
155
|
+
bitmap.close?.();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** An image value or a path, as the asset reference to read it by. */
|
|
159
|
+
function imageReference(value) {
|
|
160
|
+
if (typeof value === "string" && value)
|
|
161
|
+
return { path: value };
|
|
162
|
+
const candidate = value?.path;
|
|
163
|
+
return typeof candidate === "string" && candidate ? { path: candidate } : undefined;
|
|
164
|
+
}
|
|
165
|
+
export async function executeContactSheet(context) {
|
|
166
|
+
const { props } = context;
|
|
167
|
+
const references = [...context.inputs.images, ...(context.inputs.list ?? [])]
|
|
168
|
+
.map(imageReference)
|
|
169
|
+
.filter((reference) => reference !== undefined);
|
|
170
|
+
const bytes = [];
|
|
171
|
+
for (const reference of references) {
|
|
172
|
+
bytes.push(await context.capabilities.assets.read(reference, { signal: context.signal }));
|
|
173
|
+
}
|
|
174
|
+
const { bytes: sheet, layout } = await composeContactSheet(bytes, {
|
|
175
|
+
columns: props.columns,
|
|
176
|
+
gap: props.gap,
|
|
177
|
+
cellWidth: props.cellWidth,
|
|
178
|
+
labels: props.labels ? props.labels.split("\n") : [],
|
|
179
|
+
labelSize: props.labelSize,
|
|
180
|
+
background: props.background,
|
|
181
|
+
labelColor: props.labelColor,
|
|
182
|
+
});
|
|
183
|
+
const asset = await context.capabilities.assets.write(sheet, { mediaType: "image/png", suggestedName: props.filename }, { signal: context.signal });
|
|
184
|
+
context.outputs.asset.set(asset);
|
|
185
|
+
context.outputs.image.set({
|
|
186
|
+
path: asset.path ?? props.filename,
|
|
187
|
+
size: [layout.width, layout.height],
|
|
188
|
+
channels: "rgba",
|
|
189
|
+
depth: "u8",
|
|
190
|
+
space: "srgb",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
export const contactSheetRegistration = {
|
|
194
|
+
kind: "definition-v1",
|
|
195
|
+
moduleId: "cascade.image.ContactSheet",
|
|
196
|
+
definition: contactSheetDefinition,
|
|
197
|
+
loadExecute: async () => executeContactSheet,
|
|
198
|
+
};
|
|
199
|
+
//# sourceMappingURL=contactSheet.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DefinitionNodeRegistration } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The definition-v1 image nodes. One so far: the contact sheet a ladder of
|
|
4
|
+
* variants is judged on. Studio's `cascade.image.*` library (src/nodes/image)
|
|
5
|
+
* is the compatibility engine's and is not listed here.
|
|
6
|
+
*/
|
|
7
|
+
export { composeContactSheet, contactSheetDefinition, contactSheetLayout, contactSheetRegistration, executeContactSheet, type ContactSheetOptions, type SheetLayout, } from "./contactSheet.js";
|
|
8
|
+
export declare const imageNodeRegistrations: readonly DefinitionNodeRegistration[];
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { contactSheetRegistration } from "./contactSheet.js";
|
|
2
|
+
/**
|
|
3
|
+
* The definition-v1 image nodes. One so far: the contact sheet a ladder of
|
|
4
|
+
* variants is judged on. Studio's `cascade.image.*` library (src/nodes/image)
|
|
5
|
+
* is the compatibility engine's and is not listed here.
|
|
6
|
+
*/
|
|
7
|
+
export { composeContactSheet, contactSheetDefinition, contactSheetLayout, contactSheetRegistration, executeContactSheet, } from "./contactSheet.js";
|
|
8
|
+
export const imageNodeRegistrations = Object.freeze([contactSheetRegistration]);
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
package/dist/stage.js
CHANGED
|
@@ -53,7 +53,10 @@ async function overHttp(stage, args, signal) {
|
|
|
53
53
|
const postStage = async () => fetch("/api/exec", {
|
|
54
54
|
method: "POST",
|
|
55
55
|
headers: { "Content-Type": "application/json", "X-Cascade-Exec-Capability": await capability() },
|
|
56
|
-
|
|
56
|
+
// `cook`: a node's cook is a pure function of its inputs, so the server's
|
|
57
|
+
// default stage cache may serve it (see StageCachePolicy). A panel posts
|
|
58
|
+
// through its own api.runStage, without this, and is never cached unasked.
|
|
59
|
+
body: JSON.stringify({ stage, args, caller: "cook" }),
|
|
57
60
|
signal
|
|
58
61
|
});
|
|
59
62
|
let response;
|
package/doc/CLI_REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CLI reference: editing graphs
|
|
2
2
|
|
|
3
|
-
Everything an agent needs to build or change a `.cascade` graph without hand-editing JSON. Each command is one graph concept (node, wire, parameter, subnet), checks the edit with the same code as `cascade check`, and writes the file atomically.
|
|
3
|
+
Everything an agent needs to build or change a `.cascade` graph without hand-editing JSON. Each command is one graph concept (node, wire, parameter, subnet), checks the edit with the same code as `cascade check`, and writes the file atomically. Running, rendering, exporting and locks are listed under [The other commands](#the-other-commands); see also `cascade --help`, `doc/EDITOR_EXPORT.md` and `doc/WEB_PLAYER.md`.
|
|
4
4
|
|
|
5
5
|
## The guarantees
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@ Everything an agent needs to build or change a `.cascade` graph without hand-edi
|
|
|
15
15
|
| --- | --- |
|
|
16
16
|
| `cascade graph create <graph> [--name <title>]` | A new empty graph. Never overwrites. |
|
|
17
17
|
| `cascade graph read <graph> [--in <subnet>]` | Nodes with stored parameters, subnets with their ports, wires as `a.out -> b.in`. |
|
|
18
|
-
| `cascade graph list [dir]` | Every `.cascade` under a directory, with node and wire counts. |
|
|
18
|
+
| `cascade graph list [dir]` | Every `.cascade` under a directory, with node and wire counts. Skips `renders/`, `out/`, `dist/` and `node_modules/` below it. |
|
|
19
19
|
| `cascade node add <graph> <type> [--name <id>] [--in <subnet>] [--set k=v]... [--at x,y] [--comment <text>]` | Add a node. `<type>` is a module id or a short name: `Circle` is `cascade.geo.Circle`, `InkVocabulary` is `project.InkVocabulary`. The id defaults to the type in kebab case (`copy-to-points`, then `copy-to-points-2`). |
|
|
20
20
|
| `cascade node rm <graph> <node> [--recursive]` | Remove a node and its wires. A subnet with children needs `--recursive`. |
|
|
21
21
|
| `cascade connect <graph> <node.output> <node.input> [--no-layout]` | Wire two ports. A port can be left off when the node has one on that side. An input that was fed is rewired, and the old wire is reported. |
|
|
@@ -31,6 +31,40 @@ Everything an agent needs to build or change a `.cascade` graph without hand-edi
|
|
|
31
31
|
|
|
32
32
|
Every edit takes `--dry-run` (check, write nothing) and `--json`.
|
|
33
33
|
|
|
34
|
+
## The other commands
|
|
35
|
+
|
|
36
|
+
Running, sharing, locks and Studio. `cascade --help` has their options; `cascade render --help` and `cascade lock --help` have their own.
|
|
37
|
+
|
|
38
|
+
| Command | What it does |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `cascade [project-directory \| graph-file]` | Open Studio on a project or graph. |
|
|
41
|
+
| `cascade new <name>` | Create a Cascade project. |
|
|
42
|
+
| `cascade node <Name> [project-directory]` | Create a deterministic custom node. |
|
|
43
|
+
| `cascade projects [directory]` | Show or set the default projects directory. |
|
|
44
|
+
| `cascade serve start\|stop\|restart\|status\|logs\|install` | Run Studio as a managed background service. |
|
|
45
|
+
| `cascade run <graph-file> [options]` | Cook a graph headlessly; `--frames` renders a sequence. |
|
|
46
|
+
| `cascade render <graph-file>` | Render an animation across worker processes, then a preview video. |
|
|
47
|
+
| `cascade validate <graph-file>` | Validate a graph file without executing. |
|
|
48
|
+
| `cascade check <graph-file>` | Statically check node definitions, types and graph structure. |
|
|
49
|
+
| `cascade inspect <graph-file>` | Every node with its classification and full definition. |
|
|
50
|
+
| `cascade parity <graph-file>` | Cook headless twice and through Studio's path; report per-node differences. |
|
|
51
|
+
| `cascade build <graph-file> --out <new-directory>` | A standalone browser player ([WEB_PLAYER.md](WEB_PLAYER.md)). |
|
|
52
|
+
| `cascade export <graph-file> (--plan \| --out <dir>)` | The graph's published editor as a static site ([EDITOR_EXPORT.md](EDITOR_EXPORT.md)). |
|
|
53
|
+
| `cascade publish <graph-file> <node>.<prop>` | Publish a node prop as an editor parameter. |
|
|
54
|
+
| `cascade freeze <graph-file> <node> --fields <a,b>` | Snapshot chosen fields of a live data source for export. |
|
|
55
|
+
| `cascade scene <graph-file> <scene.json> --out <new-graph-file>` | Apply a scene file to a copy of the graph. |
|
|
56
|
+
| `cascade mcp [project-directory]` | Serve these commands as MCP tools over stdio ([MCP.md](MCP.md)). |
|
|
57
|
+
| `cascade lock <graph-file> <node> --name <name>` | Record a render (graph, params, seeds, inputs, code) in `locks/<id>.json`. |
|
|
58
|
+
| `cascade lock verify [<lock-id>...]` | Replay locks and report which still match. |
|
|
59
|
+
| `cascade replay <lock-id>` | Re-render a lock from its stored code and compare fingerprints. |
|
|
60
|
+
| `cascade locks` | List the project's locks. |
|
|
61
|
+
| `cascade ladder <graph-file> <node> --name <name> --vary <node.prop>=<a,b,c> \| --seeds <a,b,c>` | Cook 3 to 5 variants (values zipped across params, or graph seeds) from the stored code; write `ladders/<id>/<n>.png`, a labelled contact sheet `ladders/<id>/sheet.png` and the record `ladders/<id>.json` (per variant: graph, params, seed, inputs, fingerprint; the code and git commit once). |
|
|
62
|
+
| `cascade pick <ladder-id> <n> [--name <lock name>]` | At approval, write `locks/<id>.json` for variant `n` from the ladder's record, without cooking again; `cascade replay` reproduces that image pixel for pixel. |
|
|
63
|
+
| `cascade help node [<type>] [--project <dir>]` | A node type's inputs, props and outputs (type, default, range, description), where it runs and its capabilities, read from the installed package or the project's `nodes/`; without a type, every node type. No repository checkout needed. |
|
|
64
|
+
| `cascade doctor [project-directory] [--fix]` | What a headless run here will find: the declared Python environment, the stage interpreter, ffmpeg, the canvas. `--fix` builds the environment. See [Python for stages](#python-for-stages-cascadejson-python). |
|
|
65
|
+
|
|
66
|
+
Every verb is one row in `src/cli/verbs.ts`: its usage and summary for `--help`, the handler that runs it, how it treats `--json`, and the MCP tools that call it. A new verb is a row there, its handler, and its line here (`tests/cli-verbs.test.ts` checks both this page and [MCP.md](MCP.md)).
|
|
67
|
+
|
|
34
68
|
## `--json`, on every command
|
|
35
69
|
|
|
36
70
|
Every command, not only the editing ones, prints one envelope on stdout and nothing else:
|
|
@@ -98,3 +132,40 @@ cascade check index.cascade && cascade run index.cascade --frames 1 --json
|
|
|
98
132
|
```
|
|
99
133
|
|
|
100
134
|
Opened in Studio, the top level reads `motif → transform → render`, with the camera beside `transform`, and `motif` holds the circle and rectangle side by side above `copy-to-points` and its output.
|
|
135
|
+
|
|
136
|
+
## Outputs as files (`run --out`)
|
|
137
|
+
|
|
138
|
+
`cascade run <graph> --out <dir>` copies each image output to `<dir>/<node>.<ext>`, or `<dir>/<node>.<port>.<ext>` for a node with more than one image output. With `--frames` the same names carry the frame: `<node>[.<port>].<frame>.<ext>`. Which outputs: every image output nothing downstream consumes (a `cascade.core.Null` passing an image counts), or those of `--node`; `--out-node <id>` (repeatable) adds a mid-graph node's. `--out` may be project-relative or absolute; `files` in the result is spelled the same way. `run --json` lists every image output as `data.images: [{ node, port, source, file? }]`, with or without `--out`. The MCP `cook` and `render` tools take `out` and `outNodes`.
|
|
139
|
+
|
|
140
|
+
## Checks that fail a run (`cascade.assert.*`)
|
|
141
|
+
|
|
142
|
+
Put the check in the graph instead of a script beside it:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
cascade node add index.cascade NotBlank --name poster-not-blank --set 'message="the poster rendered empty"'
|
|
146
|
+
cascade connect index.cascade render.image poster-not-blank.image
|
|
147
|
+
cascade node add index.cascade cascade.assert.Range --name density-ok --set min=0.2 --set max=0.8
|
|
148
|
+
cascade connect index.cascade stats.density density-ok.value
|
|
149
|
+
cascade run index.cascade --json # exit 1: { "ok": false, "error": { "message": "Assertion failed in \"density-ok\" (cascade.assert.Range): value 0.93 is outside [0.2, 0.8]." } }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`Range` (a finite number in `[min, max]`), `Equals` (JSON-equal to `expected`, numbers within `tolerance`), `True`, and `NotBlank` (a PNG or PPM that is not one flat colour within `tolerance`, nor fully transparent). Each passes its input through and runs when the graph runs whole, or when the `--node` you name is downstream of it. An image wired only into assertions is still saved by `--out`. Headless only for now; see `doc/NODE_REFERENCE.md`.
|
|
153
|
+
|
|
154
|
+
## Debug output and environment (`.cascade/runs/`, `--env`)
|
|
155
|
+
|
|
156
|
+
Every run's trace in `.cascade/runs/<time>-<graph>.json` holds, per node, what it printed while it cooked: `console.log/info/warn/error/debug` from the node, and its Python stages' stderr, as `logs: [{ level, text }]` (500 lines per node, the rest counted in `logsDropped`). `run --json` gives the same lines as `data.logs: [{ node, level, text }]`. A failed run writes its trace too, and the error ends with `Run trace: <path>`.
|
|
157
|
+
|
|
158
|
+
`--env NAME=value` (repeatable) sets a variable for the run: Python stages, the warm stage worker and `cascade render`'s worker processes see it, so `CLOUD_ANALYST_ONLINE=1` no longer needs a shell prefix. The trace records the names under `env`, never the values. The MCP `cook` and `render` tools take `env: { NAME: "value" }`.
|
|
159
|
+
|
|
160
|
+
## Python for stages (`cascade.json` `python`)
|
|
161
|
+
|
|
162
|
+
A project whose stages import numpy or Pillow declares them, and Cascade keeps a virtual environment for it in `.cascade-cache/python/venv` (inside the project, never system-wide):
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"exec": { "stages": { "entrypoint": "runtime/node_cli.py" } },
|
|
167
|
+
"python": { "requirements": ["numpy", "Pillow>=10"] }
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`requirements` is a list of pip specifiers or a project-relative requirements file; `interpreter` (default `python3`) is what creates the venv. `cascade doctor` reports it as `current`, `missing` or `stale` (cascade.json changed since it was built); `cascade doctor --fix` creates or rebuilds it, moving an old one to `.cascade-cache/python/_archive/`. While one is declared, stages run under its interpreter instead of a bare `python`/`python3` in `exec.stages.python` (a project-relative executable there still wins), and a stage call with the environment missing or stale fails with the command that fixes it, rather than later on an `ImportError`. MCP: `doctor { "fix": true }`.
|
package/doc/EDITOR_EXPORT.md
CHANGED
|
@@ -92,7 +92,8 @@ prints the same manifest without building anything.
|
|
|
92
92
|
directory, so no `.env.local` can land in the site. It deploys to
|
|
93
93
|
production, then checks that an anonymous request to
|
|
94
94
|
`https://<project>.vercel.app` is refused. If it is not, it says to turn on
|
|
95
|
-
Vercel Authentication.
|
|
95
|
+
Vercel Authentication. Add `--public` when the editor is meant to be open to
|
|
96
|
+
anyone: a readable site is then reported as intended instead of failing.
|
|
96
97
|
|
|
97
98
|
## The `editor` section
|
|
98
99
|
|
|
@@ -121,12 +122,18 @@ Studio keeps it verbatim when it saves. The types are in
|
|
|
121
122
|
{ "id": "soft", "label": "Soft", "values": { "strength": 1 }, "keep": ["colour", "font"] }
|
|
122
123
|
],
|
|
123
124
|
"assets": ["assets/fonts/InterDisplay-Medium.otf"],
|
|
125
|
+
"fonts": [{ "family": "DM Mono", "file": "assets/fonts/DMMono-Regular.woff2", "license": "OFL-1.1", "weight": 400 }],
|
|
124
126
|
"freeze": [{ "node": "moment", "output": "moment", "snapshot": "frozen/moment.json" }],
|
|
125
127
|
"panel": { "module": "editor/panel.ts", "placement": "extend" },
|
|
126
128
|
"inspect": true
|
|
127
129
|
}
|
|
128
130
|
```
|
|
129
131
|
|
|
132
|
+
`fonts` ships open-licensed fonts with the site and loads them in the page
|
|
133
|
+
(`@font-face`) and in the engine worker (`FontFace`), so a panel or a canvas
|
|
134
|
+
that names the family draws with it. Each entry must say `"license": "OFL-1.1"`;
|
|
135
|
+
anything else, or a file under `licensed/`, is refused.
|
|
136
|
+
|
|
130
137
|
- **Parameter `id`** is the key a scene uses. It survives renaming the node
|
|
131
138
|
or the prop, so keep it stable.
|
|
132
139
|
- **`control`** is one of `number`, `slider`, `int`, `bool`, `select`
|
|
@@ -216,7 +223,9 @@ The same scene gives byte-identical output in the editor and under `cascade run`
|
|
|
216
223
|
|
|
217
224
|
- Only browser-runnable definition-v1 nodes ship. Python, shell and dynamic
|
|
218
225
|
nodes are listed together as "not exportable". Port them to TypeScript, or
|
|
219
|
-
freeze their output.
|
|
226
|
+
freeze their output. A node that draws ships when it uses `cascade/image`
|
|
227
|
+
(PROJECT_AUTHORING.md, "Draw an image in any host"); fonts and images it
|
|
228
|
+
reads must be shipped files (`editor.assets`, `editor.fonts`, frozen files).
|
|
220
229
|
- The editor cooks in a module worker. Unchanged stages are reused
|
|
221
230
|
(`run({ reuse: true })`), so only the stages after an edit re-cook, and the
|
|
222
231
|
page stays responsive while they do. Values cross to the page by structured
|
package/doc/MCP.md
CHANGED
|
@@ -37,8 +37,13 @@ With a global install (`npm i -g @field/cascade`), use `"command": "cascade", "a
|
|
|
37
37
|
| `render` | `cascade run <graph> --frames … [--fps] [--out] [--node]… [--scene]` |
|
|
38
38
|
| `export` | `cascade export <graph> --plan \| --out <dir>` |
|
|
39
39
|
| `lock`, `replay`, `locks` | `cascade lock <graph> <node> --name`, `cascade replay <id> [--live]`, `cascade locks` |
|
|
40
|
+
| `ladder`, `pick` | `cascade ladder <graph> <node> --name --vary node.prop=a,b,c \| --seeds a,b,c` (`vary` is `{ "node.prop": [a, b, c] }`), `cascade pick <ladder> <n>` |
|
|
41
|
+
| `node_help` | `cascade help node [<type>]` (a node type's declaration; every type without one) |
|
|
42
|
+
| `doctor` | `cascade doctor [--fix]` (Python environment, stage interpreter, ffmpeg, canvas; `fix` builds the environment) |
|
|
40
43
|
|
|
41
|
-
The editing tools take `dryRun`. Reads and edits run inside the server through the same functions the CLI commands call. Cooking, rendering, checking and
|
|
44
|
+
The editing tools take `dryRun`. Reads and edits run inside the server through the same functions the CLI commands call. Cooking, rendering, checking, exporting and the lock tools run `cascade <command> --json` as one child process each, so a long render leaves nothing behind in the server and returns exactly what the CLI would print. Calls run one at a time, so two edits to one file never interleave.
|
|
45
|
+
|
|
46
|
+
Each tool is an `mcp` entry on its command's row in `src/cli/verbs.ts`, with its description and input schema, so a tool cannot name a command this build does not have (`tests/cli-verbs.test.ts` checks every tool against the table and this page).
|
|
42
47
|
|
|
43
48
|
## A session
|
|
44
49
|
|
|
@@ -51,3 +56,14 @@ render { "graph": "index.cascade", "frames": "1-48", "out": "renders/check
|
|
|
51
56
|
```
|
|
52
57
|
|
|
53
58
|
See `doc/CLI_REFERENCE.md` for what each operation guarantees about diffs, placement and subnet boundaries.
|
|
59
|
+
|
|
60
|
+
## Another project, per call
|
|
61
|
+
|
|
62
|
+
Every tool takes an optional `project`: the project directory for that call, absolute or relative to the directory the server was started in. Without it nothing changes. With it, the call's paths (`graph`, `out`, `scene`, `directory`) resolve against that project and may not leave it, and `cook`, `render`, `check` and the others run their CLI child there. So one server started in `commons` can edit and render a sketch next to it:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
graph_read { "project": "/Users/me/Documents/Cascade/cloud-volumes", "graph": "index.cascade" }
|
|
66
|
+
cook { "project": "../cloud-volumes", "graph": "index.cascade", "out": "renders/check" }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
A `project` that is not a directory is refused.
|
package/doc/NODE_REFERENCE.md
CHANGED
|
@@ -589,6 +589,39 @@ Runs on `portable`.
|
|
|
589
589
|
| --- | --- | --- | --- |
|
|
590
590
|
| `geometry` | `geometry` | | |
|
|
591
591
|
|
|
592
|
+
### `cascade.image.ContactSheet`
|
|
593
|
+
|
|
594
|
+
Contact Sheet — Lay images out as one labelled grid.
|
|
595
|
+
|
|
596
|
+
Runs on `portable`.
|
|
597
|
+
|
|
598
|
+
**Inputs**
|
|
599
|
+
|
|
600
|
+
| name | type | default | range |
|
|
601
|
+
| --- | --- | --- | --- |
|
|
602
|
+
| `images` | `image` | | |
|
|
603
|
+
| `list` | `array` | | |
|
|
604
|
+
|
|
605
|
+
**Props**
|
|
606
|
+
|
|
607
|
+
| name | type | default | range |
|
|
608
|
+
| --- | --- | --- | --- |
|
|
609
|
+
| `columns` | `int` | `0` | 0…64 |
|
|
610
|
+
| `gap` | `int` | `16` | 0…512 |
|
|
611
|
+
| `background` | `color` | `[1,1,1,1]` | |
|
|
612
|
+
| `cellWidth` | `int` | `0` | 0…8192 |
|
|
613
|
+
| `labels` | `string` | `""` | |
|
|
614
|
+
| `labelSize` | `int` | `14` | 6…128 |
|
|
615
|
+
| `labelColor` | `color` | `[0.1,0.1,0.1,1]` | |
|
|
616
|
+
| `filename` | `string` | `"contact-sheet.png"` | |
|
|
617
|
+
|
|
618
|
+
**Outputs**
|
|
619
|
+
|
|
620
|
+
| name | type | default | range |
|
|
621
|
+
| --- | --- | --- | --- |
|
|
622
|
+
| `image` | `image` | | |
|
|
623
|
+
| `asset` | `asset` | | |
|
|
624
|
+
|
|
592
625
|
### `cascade.math.Binary`
|
|
593
626
|
|
|
594
627
|
Binary Math — Two values, one operation. Component-wise, and a scalar broadcasts against a vector.
|
|
@@ -973,6 +1006,115 @@ Runs on `portable`.
|
|
|
973
1006
|
| --- | --- | --- | --- |
|
|
974
1007
|
| `scene` | `scene` | | |
|
|
975
1008
|
|
|
1009
|
+
## Assertions, headless only
|
|
1010
|
+
|
|
1011
|
+
`cascade.assert.*` fail a `cascade run` (non-zero exit, the message in
|
|
1012
|
+
`run --json`'s `error.message`) when their check does not hold, and pass
|
|
1013
|
+
their input through. The CLI registers them; Studio does not yet, and shows
|
|
1014
|
+
them as unresolved. An image wired only into assertions still counts as the
|
|
1015
|
+
graph's final image for `run --out`.
|
|
1016
|
+
|
|
1017
|
+
### `cascade.assert.Equals`
|
|
1018
|
+
|
|
1019
|
+
Assert Equals — Fails the run unless value equals expected (JSON-equal; numbers within tolerance); passes value through
|
|
1020
|
+
|
|
1021
|
+
Runs on `portable`.
|
|
1022
|
+
|
|
1023
|
+
**Inputs**
|
|
1024
|
+
|
|
1025
|
+
| name | type | default | range |
|
|
1026
|
+
| --- | --- | --- | --- |
|
|
1027
|
+
| `value` | `any` | `null` | |
|
|
1028
|
+
|
|
1029
|
+
**Props**
|
|
1030
|
+
|
|
1031
|
+
| name | type | default | range |
|
|
1032
|
+
| --- | --- | --- | --- |
|
|
1033
|
+
| `expected` | `any` | `null` | |
|
|
1034
|
+
| `tolerance` | `float` | `0` | |
|
|
1035
|
+
| `message` | `string` | `""` | |
|
|
1036
|
+
|
|
1037
|
+
**Outputs**
|
|
1038
|
+
|
|
1039
|
+
| name | type | default | range |
|
|
1040
|
+
| --- | --- | --- | --- |
|
|
1041
|
+
| `value` | `any` | | |
|
|
1042
|
+
|
|
1043
|
+
### `cascade.assert.NotBlank`
|
|
1044
|
+
|
|
1045
|
+
Assert Not Blank — Fails the run when an image (PNG or PPM) is one flat colour or fully transparent; passes the image through
|
|
1046
|
+
|
|
1047
|
+
Runs on `portable`.
|
|
1048
|
+
|
|
1049
|
+
**Inputs**
|
|
1050
|
+
|
|
1051
|
+
| name | type | default | range |
|
|
1052
|
+
| --- | --- | --- | --- |
|
|
1053
|
+
| `image` | `image` | | |
|
|
1054
|
+
|
|
1055
|
+
**Props**
|
|
1056
|
+
|
|
1057
|
+
| name | type | default | range |
|
|
1058
|
+
| --- | --- | --- | --- |
|
|
1059
|
+
| `tolerance` | `int` | `0` | 0…255 |
|
|
1060
|
+
| `message` | `string` | `""` | |
|
|
1061
|
+
|
|
1062
|
+
**Outputs**
|
|
1063
|
+
|
|
1064
|
+
| name | type | default | range |
|
|
1065
|
+
| --- | --- | --- | --- |
|
|
1066
|
+
| `image` | `image` | | |
|
|
1067
|
+
|
|
1068
|
+
### `cascade.assert.Range`
|
|
1069
|
+
|
|
1070
|
+
Assert Range — Fails the run unless value is a finite number in [min, max]; passes value through
|
|
1071
|
+
|
|
1072
|
+
Runs on `portable`.
|
|
1073
|
+
|
|
1074
|
+
**Inputs**
|
|
1075
|
+
|
|
1076
|
+
| name | type | default | range |
|
|
1077
|
+
| --- | --- | --- | --- |
|
|
1078
|
+
| `value` | `float` | `0` | |
|
|
1079
|
+
|
|
1080
|
+
**Props**
|
|
1081
|
+
|
|
1082
|
+
| name | type | default | range |
|
|
1083
|
+
| --- | --- | --- | --- |
|
|
1084
|
+
| `min` | `float` | `0` | |
|
|
1085
|
+
| `max` | `float` | `1` | |
|
|
1086
|
+
| `message` | `string` | `""` | |
|
|
1087
|
+
|
|
1088
|
+
**Outputs**
|
|
1089
|
+
|
|
1090
|
+
| name | type | default | range |
|
|
1091
|
+
| --- | --- | --- | --- |
|
|
1092
|
+
| `value` | `float` | | |
|
|
1093
|
+
|
|
1094
|
+
### `cascade.assert.True`
|
|
1095
|
+
|
|
1096
|
+
Assert True — Fails the run unless value is true (wire a condition, or give the prop an expression); passes value through
|
|
1097
|
+
|
|
1098
|
+
Runs on `portable`.
|
|
1099
|
+
|
|
1100
|
+
**Inputs**
|
|
1101
|
+
|
|
1102
|
+
| name | type | default | range |
|
|
1103
|
+
| --- | --- | --- | --- |
|
|
1104
|
+
| `value` | `bool` | `false` | |
|
|
1105
|
+
|
|
1106
|
+
**Props**
|
|
1107
|
+
|
|
1108
|
+
| name | type | default | range |
|
|
1109
|
+
| --- | --- | --- | --- |
|
|
1110
|
+
| `message` | `string` | `""` | |
|
|
1111
|
+
|
|
1112
|
+
**Outputs**
|
|
1113
|
+
|
|
1114
|
+
| name | type | default | range |
|
|
1115
|
+
| --- | --- | --- | --- |
|
|
1116
|
+
| `value` | `bool` | | |
|
|
1117
|
+
|
|
976
1118
|
## Studio node libraries
|
|
977
1119
|
|
|
978
1120
|
Class-based nodes, available in Studio. Their parameters are declared in the
|