Live-Konsole + Progress-Bars — Engine.log + Engine.progress
Log-Zeilen und Progress-Bars ans UI / die CLI streamen, **während das Skript läuft**, nicht erst danach. `Engine.log("…")` erscheint sofort im Console-Panel; ein `Engine.progress("Encoding", { total: 40 })`-Handle treibt einen inline Progress-Balken, der bei jedem `p.step()` tickt. Mehrere Handles können gleichzeitig aktiv sein — jeder bekommt seinen eigenen Balken. Drei Muster gezeigt: 1. **Indeterminate Progress** (Spinner-Style) für Arbeit ohne bekannte Stückzahl. `.message("…")` aktualisiert nur den Status-Text, ohne den Wert zu ändern. 2. **Determinate Progress** (gefüllter Balken) für Schleifen mit bekanntem Total. `.step(delta, msg)` schiebt vor und aktualisiert optional die Statuszeile. 3. **Mehrere parallele Progresses** — beliebig viele Handles deklarieren; das UI stapelt sie inline. Zusammen mit Batch-Modus oder länger laufenden ML-Tools sieht der Nutzer endlich, was das Skript gerade macht.
// 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
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, `column ${i + 1}/${FRAMES}`);
}
const row = frames[0].montageH(...frames.slice(1));
stitch.done("✓ montage ready");
row.save(OUTPUT);
Engine.log("done — saved to OUTPUT");
// Cleanup — free every frame + the reference. The original src too.
for (const f of frames) f.free();
reference.free();
row.free();
src.free();
// © 2026 Michael Lechner · mlc OpticScript · https://mlcgo.eu · Elastic License 2.0