TypeScript API

ProtoTwin Simulate and Connect support custom scripted components. An API is provided to allow scripted components to interface with the ProtoTwin simulation environment. The API can be used to create:

  • Components
  • Robot Programs
  • Value Converters
  • Tools
  • Models with Torq

Components

The Component class is the base class for all built-in and custom scripted components created in ProtoTwin. An entity in ProtoTwin may be assigned one or more components. Components can be used to provide entities with additional data and behavior. Please see the Components page for more details. A basic example is provided below:

import { type Entity, Component, Vec3 } from "prototwin";

export class Controller extends Component {
    public scale: number = 0.75;

    constructor(entity: Entity) {
        super(entity);
    }

    public override initialize(): void {
        this.entity.position = new Vec3(0, 2.5, 0);
    }

    public override update(dt: number): void {
        this.entity.scale = this.scale;
    }
}

Robot Programs

The RobotProgramComponent class is the base class for all scripted robot programs. Please see the Robot Controller page for more details. A basic example of a scripted robot program is provided below:

import { type Entity, type RobotControllerComponent, RobotProgramComponent, Future, Sequence, Util, Wait } from "prototwin";

export class ScriptedRobotProgram extends RobotProgramComponent {
    constructor(entity: Entity) {
        super(entity);
    }

    public execute(controller: RobotControllerComponent): Future<void> {
        const sequence = new Sequence(false);
        sequence.add(() => controller.moveServo(0, Util.radians(90))); // Move the 1st joint of the robot to an angle of 90 degrees.
        sequence.add(() => Wait.seconds(2)); // Wait for 2 seconds.
        sequence.add(() => controller.moveServo(1, Util.radians(45))); // Move the 2nd joint of the robot to an angle of 45 degrees.
        return sequence.run();
    }
}

Value Converters

The IValueConverter interface is the interface for (bidirectional) value converters. Converters allow you to map values before the values are transferred between ProtoTwin and the PLC. Please see the value converters page for more details. A basic example of a bidirectional value converter is provided below:

import { type IValueConverter } from "prototwin";

// Bidirectional value converter that maps a (ProtoTwin) model boolean value to a (PLC) server numeric value, and vice versa.
export class BooleanToNumberConverter implements IValueConverter<boolean, number> {
    serverValue(modelValue: boolean): number {
        return modelValue ? 1 : 0;
    }

    modelValue(serverValue: number): boolean {
        return severValue === 1;
    }
}

Tools

The Tool class is the base class for all tools. Tools in ProtoTwin are different from the tools that the AI agent Torq can execute. Tools allow you to write a script that you can call through the UI to programmatically modify a model at design time. Please see the Tools page for more information. A basic example of a world tool is provided below:

import { type World, type Handle, Tool, GraphicsComponent, Material } from "prototwin";

export class ReplaceMaterials extends Tool<World> {
    public currentMaterial: Handle<Material> = this.handle(Material);
    public replacementMaterial: Handle<Material> = this.handle(Material);

    public override valid(world: World): boolean {
        return true;
    }

    public override execute(world: World): void {
        // Replace the graphics material of all entities in the world that have the current material with the replacement material.
        const descendants = world.descendants;
        for (const entity of descendants) {
            const graphics = entity.findComponent(GraphicsComponent);
            if (graphics !== null) {
                const material = graphics.material;
                if (material !== null && this.currentMaterial.value !== null && material.fullname === this.currentMaterial.value.fullname) {
                    graphics.material = this.replacementMaterial.value;
                }
                else if (material === null && this.currentMaterial.value === null) {
                    graphics.material = this.replacementMaterial.value;
                }
            }
        }
    }
}

Torq

The AI Agent also has access to the TypeScript API, which allows Torq to read the contents of an existing ProtoTwin model or make modifications to a model. Torq can execute the run_script tool to search for information in an existing ProtoTwin model. A basic example of an immutable script is provided below:

import { type World, RobotControllerComponent } from "prototwin";

export default function(world: World): void {
    // Which entities in the model have a robot controller component and what are their names?
    let robots: Entity[] = [];

    const entities = world.descendants;
    for (const entity of entities) {
        if (entity.hasComponent(RobotControllerComponent)) {
            robots.push(entity);
            console.log(`Entity ${entity.name} with robot controller found in the model.`);
        }
    }

    if (robots.length <= 0) {
        console.log("No entity with a robot controller component was found in the model.");
    }
}

If the script is detected as immutable, Torq will be permitted to execute the script without approval from the user, assuming Torq has the Read Only or Read Write model permission levels. For mutable scripts, Torq must be granted Read Write model permissions. A basic example of a mutable script is provided below:

import { type World, GraphicsComponent, PhysicsComponent, Geometry, Body, Collider, Vec3 } from "prototwin";

export default function(world: World): void {
    // Create a stack of 10 boxes resting on the floor with physics. The boxes should have a width, height and depth of 0.5 meters.
    const count = 10;
    const size = 0.5;
    const stack = world.create("Stack");

    console.log("Created stack parent:", stack);

    for (let i = 0; i < count; ++i) {
        const box = world.create(`Box ${i + 1}`);
        box.parent = stack;
        box.position = new Vec3(0, size * 0.5 + size * i, 0);

        const graphics = box.addComponent(GraphicsComponent);
        if (graphics !== null) {
            graphics.geometry = Geometry.Box;
            graphics.scale = new Vec3(size, size, size);
        }

        const physics = box.addComponent(PhysicsComponent);
        if (physics !== null) {
            physics.body = Body.Dynamic;
            physics.collider = Collider.Box;
        }
    }

    console.log("Stack child count:", stack.children!.length ?? 0);
}