Add & Remove Elements
This guide shows how to use the controller API to add and remove elements programmatically from the server. A 5×5 grid of colored rectangles is filled one by one in random order, then cleared in the same way — looping indefinitely.
Overview
The example is split into two scripts:
- INIT — gets the controller, clears any previous elements, creates a black image and sets it.
- SIMPLE — runs the fill/remove loop.
The key methods used are ctrl.addElement() and ctrl.removeElement(). Each element's numeric ID — obtained via rect.getId() — is stored in a lookup array so it can be removed later by reference.
INIT script
global var ctrl = Visu.page0.display1.getController();
ctrl.clearElements();
var IMAGE_SIZE = 1000;
var image = new Ve.Image(IMAGE_SIZE, IMAGE_SIZE, Ve.EPixelFormat.RGB8, [0, 0, 0]);
ctrl.setImage(image);
ctrl.zoom("frame");
Visu.update();
clearElements() removes all elements from the previous run before the new image is set. ctrl is declared global so the SIMPLE script can access it without calling getController() again.
SIMPLE script
Setup
var IMAGE_SIZE = 1000;
var GRID_SIZE = 5;
var CELL_SIZE = IMAGE_SIZE / GRID_SIZE;
var TOTAL_CELLS = GRID_SIZE * GRID_SIZE;
var GAP = 4;
var DELAY_MS = 80;
var COLORS: string[] = ["#cdff12", "#ff4444", "#44aaff", "#ff8800", "#cc44ff"];
With a 5×5 grid on a 1000×1000 image each cell is 200×200 px. GAP leaves a 4 px black border between cells so the grid lines remain visible.
Cell tracking
var cellIds: number[] = [];
for (var i = 0; i < TOTAL_CELLS; i++) {
cellIds.push(-1);
}
var emptyCells: number[] = [];
for (var i = 0; i < TOTAL_CELLS; i++) {
emptyCells.push(i);
}
var filledCells: number[] = [];
cellIds maps each cell index to the ID of the element currently occupying it (-1 when empty). emptyCells and filledCells track which cells are available at any moment.
Fill phase
while (emptyCells.length > 0) {
var fillPickIdx = Math.randint(0, emptyCells.length - 1);
var fillCell = emptyCells[fillPickIdx];
var row = Math.floor(fillCell / GRID_SIZE, 0);
var col = fillCell % GRID_SIZE;
var side = CELL_SIZE - GAP;
var cx = col * CELL_SIZE;
var cy = row * CELL_SIZE;
var colorIdx = Math.randint(0, COLORS.length - 1);
var color = COLORS[colorIdx];
var rect = new Ve.Rect2d(cx, cy, side, side, 0, false);
rect.filled = true;
rect.fillColor = color;
rect.fillOpacity = 0.2;
rect.lineColor = color;
ctrl.addElement(rect as Ve.VisionElement, "cells", { skipFullRefresh: true } as DisplayElementOptions);
cellIds[fillCell] = rect.getId();
emptyCells[fillPickIdx] = emptyCells[emptyCells.length - 1];
emptyCells.pop();
filledCells.push(fillCell);
Visu.update();
waitTime(DELAY_MS);
}
A random cell is picked from emptyCells on each iteration. After the element is added, rect.getId() returns its unique numeric ID which is stored in cellIds for later removal. The swap-remove pattern (arr[i] = arr[last]; arr.pop()) gives O(1) removal from the tracking arrays.
skipFullRefresh: true skips the full scene tree walk after each add. It is safe here because none of the elements have parent/child dependencies, and it improves performance significantly when adding many elements in a tight loop.
Remove phase
while (filledCells.length > 0) {
var removePickIdx = Math.randint(0, filledCells.length - 1);
var removeCell = filledCells[removePickIdx];
ctrl.removeElement(cellIds[removeCell]);
cellIds[removeCell] = -1;
filledCells[removePickIdx] = filledCells[filledCells.length - 1];
filledCells.pop();
emptyCells.push(removeCell);
Visu.update();
waitTime(DELAY_MS);
}
removeElement() takes the numeric ID stored earlier. Once removed, the cell is returned to emptyCells so the next fill phase can reuse it.
Full scripts
INIT
global var ctrl = Visu.page0.display1.getController();
ctrl.clearElements();
var IMAGE_SIZE = 1000;
var image = new Ve.Image(IMAGE_SIZE, IMAGE_SIZE, Ve.EPixelFormat.RGB8, [0, 0, 0]);
ctrl.setImage(image);
ctrl.zoom("frame");
Visu.update();
SIMPLE
var IMAGE_SIZE = 1000;
var GRID_SIZE = 5;
var CELL_SIZE = IMAGE_SIZE / GRID_SIZE;
var TOTAL_CELLS = GRID_SIZE * GRID_SIZE;
var GAP = 4;
var DELAY_MS = 80;
var COLORS: string[] = ["#cdff12", "#ff4444", "#44aaff", "#ff8800", "#cc44ff"];
var cellIds: number[] = [];
for (var i = 0; i < TOTAL_CELLS; i++) {
cellIds.push(-1);
}
var emptyCells: number[] = [];
for (var i = 0; i < TOTAL_CELLS; i++) {
emptyCells.push(i);
}
var filledCells: number[] = [];
while (true) {
// Phase 1: randomly fill all cells
while (emptyCells.length > 0) {
var fillPickIdx = Math.randint(0, emptyCells.length - 1);
var fillCell = emptyCells[fillPickIdx];
var row = Math.floor(fillCell / GRID_SIZE, 0);
var col = fillCell % GRID_SIZE;
var side = CELL_SIZE - GAP;
var cx = col * CELL_SIZE;
var cy = row * CELL_SIZE;
var colorIdx = Math.randint(0, COLORS.length - 1);
var color = COLORS[colorIdx];
var rect = new Ve.Rect2d(cx, cy, side, side, 0, false);
rect.filled = true;
rect.fillColor = color;
rect.fillOpacity = 0.2;
rect.lineColor = color;
ctrl.addElement(rect as Ve.VisionElement, "cells", { skipFullRefresh: true } as DisplayElementOptions);
cellIds[fillCell] = rect.getId();
emptyCells[fillPickIdx] = emptyCells[emptyCells.length - 1];
emptyCells.pop();
filledCells.push(fillCell);
Visu.update();
waitTime(DELAY_MS);
}
// Phase 2: randomly remove all cells
while (filledCells.length > 0) {
var removePickIdx = Math.randint(0, filledCells.length - 1);
var removeCell = filledCells[removePickIdx];
ctrl.removeElement(cellIds[removeCell]);
cellIds[removeCell] = -1;
filledCells[removePickIdx] = filledCells[filledCells.length - 1];
filledCells.pop();
emptyCells.push(removeCell);
Visu.update();
waitTime(DELAY_MS);
}
}