Scripting
You can add your own logic to realvirtual WEB in three places: inside the model, inside the project folder, or inside the viewer application. They differ in where the code lives, what it is bound to and how much it may do. Most model logic belongs in the first place, as a script in the GLB.
Three places for code
Section titled “Three places for code”| Script in the model | Project code | Viewer plugin | |
|---|---|---|---|
| Lives in | the GLB, on a node (WebComponent) | the project folder (plugins/*.ts) | the viewer source, built with Vite |
| Bound to | the node it sits on | the document whose scriptRef names it | every model loaded into that build |
| Language | JavaScript or TypeScript, saved as JavaScript | TypeScript | TypeScript |
| Runs | sandboxed in QuickJS, no DOM, network or wall clock | native, full access to the scene and the UI | native, full access to the scene and the UI |
| Travels with | the GLB file | the project folder | the viewer deployment |
| DES snapshot and determinism | yes (self.persist, self.random) | no | no |
| Typical use | component behaviour, interlocks, routing, cell coordination | one customer’s panels, KPIs or integrations | features for every model, such as XR or new panels |
Script in the model. The code is part of the model. Copy the GLB to another machine or another project and the behaviour comes with it, because the script is stored in the node’s rv_extras next to its Drive or Sensor data. Any primitive field you add to the same WebComponent appears in the script as self.prop, so one script can serve several instances with different settings.
Project code. A TypeScript module in the project’s plugins/ folder, bound to a document by scriptRef in project.json. You do not edit the JSON by hand: in the dashboard, drag the file onto the PLUGIN slot of the document card, or use Bind plugin…. Several documents can share one module, and renaming the GLB does not break the binding because the binding sits on the document row, not on a file name. The chip on the card shows whether the module is bound, loaded, missing or failed.
Viewer plugin. Part of the application itself. Use it for features that every model should get. It needs a build and a deployment of the viewer.
What happens when a model opens
Section titled “What happens when a model opens”- Model scripts never run on their own. The viewer counts them and waits until you choose Enable scripting (or the link carries
?scripts). Switching it off stops every script; switching it on starts them again for the loaded model. - Project code is resolved in two ways. If the module was compiled into the viewer build, that copy is used. Otherwise the viewer loads the compiled
.jsfile next to the referenced.tsfile in the project folder (plugins/line.ts→plugins/line.js). Customer deliveries create these files automatically; locally you build them withnpm run build:project-scripts -- <projectDir>. Code loaded this way is unsandboxed, so the viewer asks once per project before running it. Without consent the project still opens, only its code stays off. - A
scriptRefthat resolves to neither loads no code. There is no fallback by file name.
Which one to use
Section titled “Which one to use”- The logic describes how a part of the machine behaves: script in the model.
- The logic belongs to one customer project, such as a panel, a KPI or a link to an external system: project code.
- The logic is a feature for every model: viewer plugin.
Only scripts in the model take part in DES snapshots and in the reproducible random streams. Put state that a DES run must restore into a model script.
Writing a script
Section titled “Writing a script”In the Editor, use Edit Script on the component, or open Script Editor (component scripts) from the side toolbar. The Script Editor checks code against the SDK types. If scripting is disabled, Enable scripting allows the model scripts to run.
This example logs a message after one simulated second:
function setup(self) { self.in(1, 'hello'); return { des: { on(hook) { if (hook === 'hello') self.log('Hello at', self.now); } }, };}The self SDK
Section titled “The self SDK”| Use | SDK entry points |
|---|---|
| Configuration and simulation time | self.prop, self.now |
| Find nodes and components | self.node, self.find, self.findAll, self.children |
| Equipment and signals | self.drive, self.sensor, self.belt, self.source, self.signal |
| Material flow | self.inputs, self.outputs, self.transfer, self.spawn |
| Schedule events | self.in, self.at, self.every, self.cancel |
| State and random numbers | self.persist, self.setState, self.random |
| Script communication | self.component, self.broadcast |
| Statistics and messages | self.statValue, self.statState, self.log, self.warn, self.error |
| Geometry values | self.vec3, self.quat, self.mat4, self.aabb |
Scripts cannot access the DOM, network or wall-clock timers. The DES code check also looks for state that would be lost on restore. See Scripting in DES before adding changing state or recurring timers.