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 injected names
t,dt,propar. - Language keywords (
for,while,class,def,import, …). - Other declared port ids on the same block.
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
- JavaScript vs Python: JS runs natively (fastest); Python runs via Pyodide (slower, ~2–4 s cold start) — use JS for high-frequency loops
- Hot-reload: While the simulation is running, saving the script hot-reloads it in the worker — no restart needed
- Port collisions: Port names must not clash with runtime variables (t, dt, propar) or language keywords
- Hardware access: Use
propar.write()for real-time Bronkhorst MFC control; writes are queued to the main thread - Error handling: Runtime errors are rate-limited in the console; the block face turns red and displays the latest error message
🏎 Performance tips
- Scripts run synchronously inside the simulation step. Heavy Python work caps the achievable simulation frequency.
- First-run Pyodide cold start takes ~2–4 seconds; the Console and the block's status dot show "Loading Pyodide…" until it's ready.
- Identical runtime errors are deduplicated so the console doesn't flood — fix the first one and the rest will too.