Live console + progress bars — Engine.log + Engine.progress
Stream log lines and progress bars to the UI / CLI **while the script runs**, not just after it finishes. `Engine.log("…")` appears in the Console panel the instant it's called; an `Engine.progress("Encoding", { total: 40 })` handle drives an inline progress bar that ticks up every `p.step()`. Multiple handles can be active simultaneously — each gets its own bar. Three patterns shown: 1. **Indeterminate progress** (spinner-style) for work where you don't know the unit count up front. `.message("…")` updates the status text without changing a value. 2. **Determinate progress** (filled bar) for loops with a known total. `.step(delta, msg)` advances and optionally updates the status line. 3. **Multiple parallel progresses** — declare as many handles as you need; the UI stacks them inline. Pair this with batch mode or long-running ML tools and the user finally sees what the script is doing.
// Live console + progress bars — Engine.log() + Engine.progress()
// stream straight to the v3 UI's console panel and the CLI's
// indicatif renderer as work happens. No more waiting for the
// script to finish before output appears.
// demo_progress.js
//!INPUT: INPUT
//!OUTPUT: OUTPUT
//!PARAM: FRAMES:integer=12,min=4,max=60
//!PARAM: COLS:integer=4,min=1,max=12
const src = Engine.loadImage(INPUT);
// 1) Indeterminate progress — no `total`, just a "still working"
// spinner. The UI shows a moving pulse; the CLI shows a spinner.
const setup = Engine.progress("Preparing");
setup.message("loading reference frame");
const reference = src.clone().resize(256, 256);
setup.message("computing hue cycle");
setup.done("ready");
// 2) Determinate progress — the bar fills from 0 to FRAMES. Each
// step() call carries a status message that the UI / CLI shows
// next to the percentage.
const pass = Engine.progress("Animating hue cycle", { total: FRAMES });
const frames = [];
for (let i = 0; i < FRAMES; i++) {
const t = i / (FRAMES - 1);
const frame = reference.clone()
.hue(t * 360)
.saturation(1 + 0.5 * Math.sin(t * Math.PI));
frames.push(frame);
Engine.log(`frame ${i + 1}/${FRAMES} — hue=${Math.round(t * 360)}°`);
pass.step(1, `frame ${i + 1}/${FRAMES}`);
}
pass.done(`✓ ${FRAMES} frames rendered`);
// 3) A second determinate progress, in parallel with the first if
// the script were async — here it just shows that multiple
// handles can be active independently.
// One montageH() call with all frames as args allocates a single
// output handle — avoids the N-1 intermediates a fold loop would
// leak. The progress bar ticks for visual feedback even though the
// actual stitch is one engine call.
const stitch = Engine.progress("Stitching montage", { total: FRAMES });
for (let i = 1; i < FRAMES; i++) {
stitch.step(1, `frame ${i + 1}/${FRAMES}`);
}
// Raster statt einer Reihe. Zwoelf Bilder nebeneinander ergeben ein
// Seitenverhaeltnis von 12:1 — eine Galerie bringt das auf
// Spaltenbreite, und danach ist jedes Bild ein paar Dutzend Pixel
// breit. Bei COLS Spalten bleibt es lesbar; 4 x 3 kommt bei zwoelf
// Bildern genau auf 1024 x 768 auf.
//
// montageH fuegt eine Zeile zusammen, montageV stapelt die Zeilen.
// Die Zwischenhandles gehoeren uns und werden freigegeben — bei
// einer 4 x 3 sind es drei Stueck, die sonst bis Skriptende liegen
// blieben.
const cols = Math.max(1, Math.min(COLS, frames.length));
const rows = [];
for (let i = 0; i < frames.length; i += cols) {
const slice = frames.slice(i, i + cols);
rows.push(slice.length > 1 ? slice[0].montageH(...slice.slice(1)) : slice[0].clone());
}
const sheet = rows.length > 1 ? rows[0].montageV(...rows.slice(1)) : rows[0].clone();
rows.forEach((r) => r.free());
stitch.done("✓ montage ready");
sheet.save(OUTPUT);
sheet.free();
Engine.log("done — saved to OUTPUT");
// Cleanup — free every frame + the reference. The original src too.
// Die Zeilen-Zwischenhandles und das fertige Raster sind oben schon
// freigegeben, direkt nachdem sie gebraucht wurden.
for (const f of frames) f.free();
reference.free();
src.free();
// © 2026 Michael Lechner · mlc OpticScript · https://mlcgo.eu · Elastic License 2.0