Advanced

Script Block

Execute custom JavaScript or Python code at each simulation step. Define inputs and outputs dynamically via parameters.

Open Script in BlockWerk →

# Script Block

Execute custom JavaScript or Python logic inside the simulation loop. Define your own input and output ports — they become live variables in your script.

> 🚦 When to use: for nonlinear control laws, observers, fuzzy logic, swing-up controllers, custom math. Stick to standard blocks (Sum, Integrator, Gain, etc.) for plant models and linear dynamics — they are faster and easier to debug visually.

⚡ Runtime variables

Every script step receives:

| Name | Type | Description | | --------- | -------- | -------------------------------------------------------------- | | t | number | Current simulation time (seconds). | | dt | number | Sample time of this step (seconds). | | propar | object | Hardware bridge — see _Hardware control_ below. | | <input> | number | One variable per input port you declared in the Interface tab. |

Assign values to your output port names as plain variables — the runtime reads them after each step. Outputs are reset to 0 before every step; a value that is not a finite number is forced back to 0.

📝 Example — JavaScript

// Inputs:  setpoint, measurement
// Outputs: torque
const error = setpoint - measurement;
torque = 0.5 * error;

📝 Example — Python (Pyodide)

# Inputs:  setpoint, measurement
# Outputs: torque
error = setpoint - measurement
torque = 0.5 * error
if t % 1 < dt:
    print(f"[ctrl] t={t:.2f}s | err={error:+.3f}")

numpy is preloaded (np). scipy is auto-installed on demand if the script contains import scipy or from scipy …. Other packages: install via import micropip; await micropip.install("…") inside an async block.

🔌 Port rules

Port identifiers must be valid JavaScript / Python identifiers and must not collide with:

The editor's Interface tab validates this live and refuses bad names with an inline error.

🛠 Hardware control via ProPar

If your simulation talks to a Bronkhorst MFC (or any ProPar device), call:

JavaScript:

propar.write(/* node */ 1, /* procNr */ 1, /* paramNr */ 21, 50.0); // setpoint

Python (parameters are also exposed by symbolic name):

propar.instrument(1).fSetpoint = 50.0
# or
propar.write(1, 206, 50.0)   # 206 = DDE number

Writes are queued to the live proparService on the main thread.

🔐 External Script URL — security note

The External Script URL field is never auto-fetched. You must click Pull... and confirm to replace the local script. Fetched code runs with full simulation-worker privileges (including ProPar writes). Only pull from sources you trust.

♻️ Hot-reload

While a simulation is running or paused, saving the script (Ctrl+S or the Save button) hot-reloads the compiled function inside the worker — no restart needed. Pyodide stays warm; ports, language and script body all re-register live. If a port id becomes invalid, the script is dropped and an error appears in the Console tab.

🖥 Console & error surfacing

The Console tab shows live output from the worker — including print() / console.log() (Python only at the moment), compile errors, runtime exceptions and Pyodide load progress. Identical runtime errors are rate-limited; on recovery a success line is logged. The block face turns red and shows the latest error message when something is wrong.

📐 When to choose what

| Requirement | Preferred Solution | Why | | ---------------------------------------- | ------------------ | ------------------------------------------- | | Linear dynamics (1/s, G(s)) | Standard Blocks | Faster, visually inspectable. | | Simple math (sum, gain) | Standard Blocks | More readable on the canvas. | | Nonlinear / fuzzy / ANN logic | Script Block | Easier to maintain in code. | | Complex algorithms (swing-up, observers) | Script Block | High-dimensional logic is awkward visually. | | Hard real-time / ≥ 100 ksps | Plugin Block | Native WebAssembly (ABI v1). |

Remarks

🏎 Performance tips