Migrating to the New Variable System
Important
This guide is relevant if you have a URCap that was created using APIs introduced in PolyScope X 10.11 or older. Starting from PolyScope X 10.12, these APIs are deprecated.
This guide explains how to migrate from the deprecated URVariable class-based variable system to the new interface-based variable system. The new system provides better type safety, clearer separation between declarations and references, and improved support for variable state management.
Overview of Changes
The legacy variable system used class-based symbols (URVariable, URSymbol) with an inheritance hierarchy. The new system replaces these with plain TypeScript interfaces that provide clearer semantics:
Old (Deprecated) |
New (Recommended) |
Purpose |
|---|---|---|
|
|
Declaring a new variable |
|
|
Referencing an existing variable |
|
|
Variable type definition |
|
|
Type checking |
N/A |
|
UI/state consumption |
Import Changes
Old Imports (Deprecated)
import {
URVariable,
VariableValueType,
isVariable,
} from '@universal-robots/contribution-api';
New Imports (Recommended)
import {
VariableDeclaration,
VariableReference,
VariableDescription,
VariableDescriptionWithReference,
VARIABLE_VALUE_TYPES,
isVariableDeclaration,
isVariableReference,
} from '@universal-robots/contribution-api';
Key Interface Definitions
VariableDeclaration
Use this interface when your node declares (creates) a new variable:
interface VariableDeclaration {
id: string; // Unique identifier
name: string; // Variable name
valueType: (typeof VARIABLE_VALUE_TYPES)[number]; // e.g., 'integer', 'float', 'string'
_IDENTIFIER: 'VariableDeclaration'; // Type discriminator
}
VariableReference
Use this interface when your node references an existing variable:
interface VariableReference {
id: string; // ID of the referenced variable
_IDENTIFIER: 'VariableReference'; // Type discriminator
}
VariableDescription and VariableDescriptionWithReference
These interfaces describe the current state of a variable for use in the UI. They are returned by the VariableService.variables() observable:
interface VariableDescription {
name: string;
valueType: (typeof VARIABLE_VALUE_TYPES)[number];
// ... additional state properties
}
type VariableDescriptionWithReference = VariableDescription & {
reference: VariableReference;
};
Important
VariableDescription objects should be treated as immutable and should not be saved in node parameters, as their properties are subject to external changes.
Variable Value Types
Old Approach (Deprecated)
import { VariableValueType } from '@universal-robots/contribution-api';
const myType = VariableValueType.INTEGER; // Using enum
New Approach (Recommended)
import { VARIABLE_VALUE_TYPES } from '@universal-robots/contribution-api';
// VARIABLE_VALUE_TYPES is a const array:
// ['boolean', 'float', 'integer', 'pose', 'string', 'matrix', 'array', 'waypoint', 'grid', 'frame', 'timer', 'profile']
const myType: (typeof VARIABLE_VALUE_TYPES)[number] = 'integer'; // Using string literal
Creating Variables
Old Approach (Deprecated)
import {
ProgramBehaviorAPI,
VariableValueType,
} from '@universal-robots/contribution-api';
// In a behavior worker, use the SymbolService
const api = new ProgramBehaviorAPI(self);
// Creating a new variable declaration (returns URVariable)
const variable = await api.symbolService.generateVariable(
'myVar',
VariableValueType.INTEGER
);
// Creating a variable reference (URVariable with reference=true)
const reference = await api.symbolService.generateVariable(
'existingVar',
VariableValueType.INTEGER
);
reference.reference = true;
New Approach (Recommended)
import { ProgramBehaviorAPI } from '@universal-robots/contribution-api';
// In a behavior worker, use the VariableService
const api = new ProgramBehaviorAPI(self);
// Create a new variable declaration (returns VariableDeclaration)
const declaration = await api.symbolService.createVariable('myVar', 'integer');
// To reference an existing variable, store its VariableReference
// and use getVariableDescription to get the current state
const description = await api.symbolService.getVariableDescription(
variableReference
);
Migrating Node Parameters
If your node stores URVariable objects in its parameters, you should migrate to use VariableDeclaration or VariableReference depending on whether the variable is declared or referenced.
Example Node Migration
Old Node Definition
import { ProgramNode, URVariable } from '@universal-robots/contribution-api';
export interface MyNode extends ProgramNode {
type: 'my-node';
parameters: {
variable: URVariable; // Could be declaration or reference
};
}
New Node Definition
For nodes that declare variables:
import {
ProgramNode,
VariableDeclaration,
} from '@universal-robots/contribution-api';
export interface MyNode extends ProgramNode {
type: 'my-node';
version: '2.0.0';
parameters: {
variable: VariableDeclaration;
};
}
For nodes that reference existing variables:
import {
ProgramNode,
VariableReference,
} from '@universal-robots/contribution-api';
export interface MyNode extends ProgramNode {
type: 'my-node';
version: '2.0.0';
parameters: {
variableRef: VariableReference;
};
}
Using the VariableService API
The VariableService provides methods for working with variables in behavior workers:
import { ProgramBehaviorAPI } from '@universal-robots/contribution-api';
const api = new ProgramBehaviorAPI(self);
// Get all available variables (returns Observable)
api.symbolService.variables().subscribe((variables) => {
// variables is Array<VariableDescriptionWithReference>
});
// Create a new variable declaration
const declaration = await api.symbolService.createVariable('myVar', 'integer');
// Get variable description from a reference
const description = await api.symbolService.getVariableDescription(myReference);
// Upgrade a legacy URVariable to the new model
const upgraded = await api.symbolService.upgradeURVariable(legacyVariable);
Upgrading Legacy Nodes
When loading programs saved with the old variable system, use the upgradeURVariable method in your upgradeNode function:
import {
ProgramBehaviorAPI,
ProgramBehaviors,
VariableDeclaration,
VariableDescriptionWithReference,
} from '@universal-robots/contribution-api';
const behaviors: ProgramBehaviors<MyNode> = {
// ... other behaviors
upgradeNode: async (node: unknown): Promise<MyNode> => {
const oldNode = node as any;
// Check if this is an old version with URVariable
if (oldNode.version === '1.0.0' && oldNode.parameters?.variable) {
const api = new ProgramBehaviorAPI(self);
// Upgrade the legacy URVariable
const upgraded = await api.symbolService.upgradeURVariable(
oldNode.parameters.variable
);
// upgraded is either:
// - VariableDeclaration (if variable.reference was false)
// - VariableReference (if variable.reference was true)
const newNode: MyNode = {
...oldNode,
version: '2.0.0',
parameters: {
variable: upgraded as VariableDeclaration,
},
};
return newNode;
}
return node as MyNode;
},
};
Understanding upgradeURVariable Results
The upgradeURVariable method returns different types based on the input:
Input |
Output |
|---|---|
|
|
|
|
Already upgraded variable |
Returns as-is |
Type Guards
Old Approach (Deprecated)
import { isVariable, URVariable } from '@universal-robots/contribution-api';
if (isVariable(item)) {
// item is URVariable
}
New Approach (Recommended)
import {
isVariableDeclaration,
isVariableReference,
VariableDeclaration,
VariableReference,
} from '@universal-robots/contribution-api';
if (isVariableDeclaration(item)) {
// item is VariableDeclaration
console.log(item.name, item.valueType);
}
if (isVariableReference(item)) {
// item is VariableReference
console.log(item.id);
}
Complete Migration Example
Here’s a complete example showing how to migrate a program node from the old to the new variable system:
Before Migration
/// <reference lib="webworker" />
import {
ProgramBehaviorAPI,
ProgramBehaviors,
ProgramNode,
registerProgramBehavior,
ScriptBuilder,
URVariable,
VariableValueType,
} from '@universal-robots/contribution-api';
interface OldMyNode extends ProgramNode {
type: 'my-custom-node';
parameters: {
variable: URVariable;
value: string;
};
}
const behaviors: ProgramBehaviors<OldMyNode> = {
factory: async (): Promise<OldMyNode> => {
const api = new ProgramBehaviorAPI(self);
const variable = await api.symbolService.generateVariable(
'myVar',
VariableValueType.INTEGER
);
return {
type: 'my-custom-node',
allowsChildren: false,
parameters: {
variable,
value: '0',
},
};
},
generateCodeBeforeChildren: async (
node: OldMyNode
): Promise<ScriptBuilder> => {
const builder = new ScriptBuilder();
const { variable, value } = node.parameters;
if (variable.reference) {
builder.assign(variable.name, value);
} else {
builder.globalVariable(variable.name, value);
}
return builder;
},
};
registerProgramBehavior(behaviors);
After Migration
/// <reference lib="webworker" />
import {
ProgramBehaviorAPI,
ProgramBehaviors,
ProgramNode,
registerProgramBehavior,
ScriptBuilder,
VariableDeclaration,
VariableReference,
isVariableDeclaration,
} from '@universal-robots/contribution-api';
interface NewMyNode extends ProgramNode {
type: 'my-custom-node';
version: '2.0.0';
parameters: {
variable: VariableDeclaration;
value: string;
};
}
// Type for the old node version (for upgrade purposes)
interface OldMyNode extends ProgramNode {
type: 'my-custom-node';
version?: undefined;
parameters: {
variable: any; // URVariable
value: string;
};
}
const behaviors: ProgramBehaviors<NewMyNode> = {
factory: async (): Promise<NewMyNode> => {
const api = new ProgramBehaviorAPI(self);
const variable = await api.symbolService.createVariable('myVar', 'integer');
return {
type: 'my-custom-node',
version: '2.0.0',
allowsChildren: false,
parameters: {
variable,
value: '0',
},
};
},
generateCodeBeforeChildren: async (
node: NewMyNode
): Promise<ScriptBuilder> => {
const builder = new ScriptBuilder();
const { variable, value } = node.parameters;
// With the new system, declarations always create new variables
builder.globalVariable(variable.name, value);
return builder;
},
upgradeNode: async (node: unknown): Promise<NewMyNode> => {
const oldNode = node as OldMyNode;
// If already upgraded, return as-is
if ((node as any).version === '2.0.0') {
return node as NewMyNode;
}
// Upgrade from old URVariable format
const api = new ProgramBehaviorAPI(self);
const upgraded = await api.symbolService.upgradeURVariable(
oldNode.parameters.variable
);
return {
type: 'my-custom-node',
version: '2.0.0',
allowsChildren: false,
parameters: {
variable: upgraded as VariableDeclaration,
value: oldNode.parameters.value,
},
};
},
};
registerProgramBehavior(behaviors);
Summary
Replace
URVariableclass withVariableDeclaration(for declarations) orVariableReference(for references)Replace
VariableValueTypeenum withVARIABLE_VALUE_TYPESconstant arrayUse
upgradeURVariablein yourupgradeNodefunction to migrate saved programsUse the VariableService API (
createVariable,getVariableDescription,variables()) for variable operationsUse the new type guards (
isVariableDeclaration,isVariableReference) for type checking
The new system provides better type safety and clearer semantics while maintaining backwards compatibility through the upgrade mechanism.