Skip to main content

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.

Download project


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.

tip

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);
}
}