The primary control for executing robot motion. Use single click for incremental steps or the submenu for full automation. Double click on the play will perform the full automation as well.
Manage the sequence of movements for the robot arm. The counter displays your active position versus total positions.
To interact with objects in the scene, follow this logic strictly:
Direct Kinematics: Click and drag joints with the mouse.
Inverse Kinematics (IK): Hold SHIFT while dragging to solve for position.
Object Movement: Click any .stl to reveal the 3D Gizmo for relocation.
Object size from a plugin: getWorkspaceObjectBoundingBox(objectIndex) returns the object's axis-aligned box in world coordinates, { min, max, size, center } in mm with its rotation and position applied, for example to place the tool above an object: center[2] + size[2] / 2 is its top. It returns null while a robot is carrying the object.
Zoom: Mouse wheel, or a two-finger pinch on a touch screen: spreading the fingers zooms in, bringing them together zooms out, in proportion to the finger movement.
Linear rail: Drag the rail carriage to slide the arm along the rail. Hold SHIFT while dragging and only the base moves: the manipulator keeps its exact world position (X, Y, Z) and orientation (Rx, Ry, Rz), the joints are re-solved for every step. Where the pose cannot be reached from the new base position inside the joint limits, the carriage stops instead of letting the tool drift.
View: Drag the background to orbit, use the wheel to zoom. Hold SHIFT and drag the background to move the view center in X / Y, hold ALT (Option on Mac) and drag for its height (Z); the scene follows the mouse. Adding a second arm centers the view between the arms, Center the view in Settings brings the center and the zoom back to their defaults, and the view center is saved with the workspace.
The simulator counts every joint positive counter-clockwise about its axis vector, with zero at the model's home pose. Real controllers differ in sign and zero, so each .rob row carries a mapping real = sign · sim + offset (columns 14 / 15: sign, offset in degrees). Every joint angle that crosses the plugin API - getCurrentJointAngles(), getHomeJointAngles() (the simulator's Home pose, all joints at 0 in the simulator's convention, as API angles), worldToJointAngles*(), jointAnglesToWorld*(), postures, trajectory deltas, limits - is in the real convention; getJointAnglesMapping() / setJointAnglesMapping() read and change the mapping at run time (e.g. [{sign: 1, offset: 0}, {sign: -1, offset: -Math.PI/2}, {sign: -1, offset: Math.PI/2}, …] for a KUKA whose home is the candle pose).
Each robot's .rob file carries the manufacturer's joint angle range (columns 12 / 13, degrees, real convention) of every joint; the simulator derives its own limits through the mapping. Dragging a segment stops at its limit, the joint sliders span the limits, and IK solutions outside them are rejected (the pivot gizmo stops, Apply XYZ finds another solution or none). Plugins can read the limits with getRobotJointLimits(), check a posture with isPostureValid(), and simulateRobotMotion() refuses a trajectory that would leave them. Models whose limits are not the manufacturer's values report a warning in the console when loaded.
Singularities: a plugin can check a Cartesian path before it is simulated with findTrajSingularity(path, q0, normal, robotIdx, K = 10). The path is solved point by point like worldToJointAnglesBatch(), and the conditioning of the arm's Jacobian at each solution is compared with the best point on the path. It returns null when the path is fine, otherwise [[index, K], …]: the path index and how many times worse that point is conditioned (0 = exactly singular, the arm loses a degree of freedom there, joints would have to spin). Points near the flagged indices should be moved, or the tool normal changed, before simulateRobotMotion(). The demo plugin shows the call.
Trajectory line: the drawn path is an anti-aliased line of constant pixel width (it does not grow with the zoom). Its width and colour are set in Settings → the palette button beside Show path (Path style window), which also holds the singularity marking: the heaviest line weight and the start / end colour of the gradient plugins use; defaults are 1 px (the grid line weight), teal, 3 px, light red to full red. A segment always has one width and colour, that of its styled end point; plugins read them with getTrajLineStyle(), change them with setTrajLineStyle(width, color), and can give single steps of the current trajectory their own width and colour with setTrajStepStyle([[stepIndex, width, [r, g, b]], …], robotIdx) ([] clears). The demo plugin colours the steps that findTrajSingularity() reports with the gradient and the weight from the Path style window (defaults: light red to full red, grid weight to 3 px), the worse the conditioning the redder and heavier the line. getTrajLineStyle() returns them as singularWidth, singularColorStart and singularColorEnd, and setTrajLineStyle(width, color, { width, colorStart, colorEnd }) changes them. A new trajectory starts with the defaults again.
Each posture in the queue carries how the arm travels from it to the next posture (Posture Queue → Posture Info): a Line set on posture 1 is the move between postures 1 and 2, and the last posture's type closes the loop back to the first. Free Move interpolates the joint angles with a trapezoidal velocity profile; Line, Arc (radius, plane normal, short / long, CW / CCW; Full circle draws a whole circle of the radius from the start position back to it when both postures coincide, CW or CCW, with the centre in the direction of the tool normal at the start turned by the starting angle about the plane normal) and 3D Curve (a spline through user mid points) move the tool centre point exactly along that path in world space. The orientation changes smoothly on the way: the three wrist joints are interpolated between the two postures with the same trapezoidal velocity profile, the other joints are solved for every path point, and the move ends exactly on the saved posture (position and orientation). The arc's plane normal and the mid point fields of a 3D curve follow the manipulator while their lock chip is closed, like the read-out in Joint Control: the normal takes the manipulator's normal when Arc is chosen and whenever you move the arm afterwards (a playing simulation does not change it), and the curve point shows the tool position, so you teach a mid point by moving the arm and pressing Add. Open the lock to type values by hand; selecting a stored point in the list opens it for editing.
The same window sets per posture a dwell time (seconds the arm pauses at the posture before the move that starts there; dwells apply to Play Full Sequence only, which opens with the dwell of its first posture, not to single steps), a velocity 0..1 for the move that starts at it (or "Use default" = the global velocity from Settings) and Plugin Action Execute: the action belongs to that posture. When the running simulation reaches a flagged posture it stops there, the plugin's async function userAction(robotIdx, postureIdx, velocity, dwellTime, gcodeString) is called, and the simulation continues (with the posture's dwell, then the next move) only after it has returned. A run that starts at a flagged posture calls its action first.
gcodeString is the text of the posture's G-Code or Custom Action box (enabled only with Plugin Action Execute), so a plugin can forward G-code lines or any custom command to a controller. For Line, Arc and 3D Curve the window offers two more settings: Align normal with the path makes the manipulator's normal follow the curve (at each point the curve normal closest to the one carried from the start pose is taken, and the first and last quarter of the path blend from the start pose's orientation into the aligned one and back into the end posture's), solved with the full-orientation IK; and Detect singularities with its threshold K (default 12): like every other setting it belongs to the move that starts at the posture. With the box checked on posture 1, the Cartesian move from posture 1 to posture 2 is checked like findTrajSingularity(), its singular steps are drawn with the Path style marking (they are on the line when the arm has arrived and posture 2 is the active one), and a console warning names them; unchecked (the default) the check is skipped and the line stays plain. In moveParams / getPostureMoveType() these are dwell, velocity (null = default), pluginAction, gcodeString, alignNormal, detectSingularities and singularityThreshold. Everything is saved in the .brw workspace file.
Milling Program is the fifth type: the move that starts at the posture runs a Mill / Sculpt program (see the Mill / Sculpt section). The arm first travels with a free move (trapezoidal velocity profile, rail and table included) from the posture to the program's first state, then the program is played (arm, rail and table together, carving the workpiece), and from its last state a free move takes the arm on to the next posture. Postures before and after it are ordinary ones, so a sequence can be "home → milling → inspection pose → home". The panel shows which program the posture uses: choosing the type attaches the program generated in the Mill / Sculpt window; Use generated program re-attaches it after a new generation and Open Mill / Sculpt opens the window. Play back turns a milling move into a free move. The program itself is not written into the .brw file (it is far too large); after loading a workspace, generate it again or set it from a plugin, otherwise the posture falls back to a free move with a console warning. The program's steps draw no path line.
From a plugin: In the plugin API the type is the integer enum brwAPI.MoveType = { FREE: 1, LINE: 2, ARC: 3, CURVE: 4, MILL: 5 }: addRobotPosture(jointAngles, index, robotIdx, moveType = 1, moveParams) takes it as an optional fourth argument (default Free Move) with moveParams = { radius, normal: [x, y, z], long, cw, full, startAngle } for an arc, { points: [[x, y, z], …] } (world, mm) for a curve or { program } for a milling program, where program is { name, states: [{ jointAngles, rail, table, cut }, …] } (joint angles in the real convention, rail travel in mm, table angle in degrees counting turns, cut = { layer, cell0, cell1, radius } on the blank's grid or null) - the form getMillingProgram(index = -1, robotIdx) returns for the program generated in the Mill / Sculpt window (index -1) or attached to a posture, so a plugin can store, modify or build one; program: null (or no program) uses the generated one; updateRobotPosture(index, robotIdx) overwrites a posture (default: the current one) with the arm's pose right now, keeping its move settings; getPostureMoveType(index, robotIdx) returns { moveType, radius, normal, long, cw, points } and setPostureMoveType(index, moveType, moveParams, robotIdx) changes an existing posture.
Set specific angles for each robot joint within its physical range. The system uses inverse kinematics calculations to position the arm. The sliders always show simulator angles with zero at the model's home pose (see Joint Conventions for the mapping to a real controller). M+ stores the current joint angles on a stack and M- recalls and removes the last stored set (last in, first out); the XYZ windows have the same pair for positions.
Kinematic configuration type: each model's .rob header carries a type (9th value): 1 for a standard six-axis articulated arm (the default) and 2 for a Universal Robots type arm (UR3, UR5, UR10e). The direct and inverse kinematics use the type of the arm they are solving for; plugins can read it with getRobotConfigType(robotIdx).
Each joint in the 3D control panel can be toggled between two modes:
Work in world coordinates relative to robot reach. Click any imported .stl object to use the from object option via the appearing gizmo.
XYZ & Orientation is a second window that adds the tool orientation as Rx, Ry, Rz (degrees, read from the arm's forward kinematics while the lock chip is closed) and solves position and orientation together with the full-orientation IK on Apply. Both windows have a Key/Mouse Input button: it opens a small window to type x, y, z and the normal or the angles, with Click in Workspace taking the next left click in the viewport as the target point (the same button exists in the 3D curve's Edit points window). Settings → Display cursor coordinates shows the world point under the mouse in a box above the console.
Access the XYZ coordinate window here. Note: All workspace windows are draggable and can be positioned anywhere in your browser.
Choose from a growing library of professional robot arms. Selecting a model will instantly replace the active arm in the scene while maintaining your coordinate space; with several arms loaded, the other arms are untouched (see Multiple Robot Arms).
Demo projects: the first link in the bar at the top, Demo Projects, downloads ready-made projects as one zip file (a window explains the steps first). Unzip it, then load a project with Import Workspace by choosing the project's folder, and click Play, or a User Command button for a project that holds only plugin code, such as the singularity demo. The collision avoidance demo shows the robot hitting a yellow box when played; Tools & Objects → Collision Avoidance with Precision 0 and Recalculate trajectory makes it pass around the box.
Save and restore your entire workspace including the robot configuration and all imported objects. The workspace is stored as a .brw file next to the STL files it refers to: every scene object, every tool of the library and every turntable workpiece (imported or generated in Mill / Sculpt, in its milled state), so a project started from scratch is complete after one export.
When manipulating objects in the scene, the active object is now visually highlighted so you can clearly see which item you are interacting with. This improves precision when working with multiple objects in close proximity.
Click precision_manufacturing to swap the active arm's model (KUKA, Yaskawa, etc.), add or remove arms and set their base positions.
getRobotCurrentPostureIdx() say so); when the arm is not at the first posture, for example after the previous run, a click on Play Full only brings it there with a free move, and the next click plays the queue. Remembered in the browser.A scene is no longer limited to one arm. Add as many robots as the cell needs, place each one at its own base position and program them side by side - from the interface or from a plugin. Below, two arms driven by one plugin, with the console reporting the run.
Each arm is a slot with its own model, base position, posture queue, trajectory and pick & place state. Workspace objects are shared, so two arms can hand parts to each other.
Open precision_manufacturing Robot Model: the chips at the top of the window list the arms as 1: Model [x, y, z]. The outlined chip is the active arm - the one the joint / XYZ controls, the posture queue, pick & place and the plugin API act on. Click a chip, or click any part of an arm in the viewport, to make it active.
.brw) and restored on import.Workspaces (.brw, version 2.1) save all arms with their base positions, postures and pick & place. Older single-arm files still load as one arm at the origin.
Robot-related API calls take an optional trailing robotIdx. Arms play concurrently, so start each one and they move together:
// second arm 1.5 m along X, same model as the active one
const b = await brwAPI.addRobotArm(-1, [1500, 0, 0]);
for (const k of [0, b])
{
const q0 = await brwAPI.getCurrentJointAngles(k);
const start = await brwAPI.getCurrentWorldPosition(k); // base included
const path = await brwAPI.worldToJointAnglesBatch(wpos(start), q0, null, k);
await brwAPI.simulateRobotMotion(steps(path), k); // returns at once
}
Open view_in_ar Tools & Objects → Tools. Import tool .stl reads one or several STL files into the tool list (named after the file). Select a tool in the list and press Attach Tool: it replaces the arm's default end effector (the 7th part of the model) and the arm's tool point moves to the tool offset - dx / dy / dz from the flange, which take the place of the 7th row of the robot's .rob file. Kinematics, the XYZ read-out, the drawn path, the pivot gizmo and pick & place all work at the tool point. Detach Tool puts the original end effector and offset back.
Model a tool with its attach point at the origin and pointing along +X; on attach it is moved to the flange (the sum of the model's joint translations, rows 1 … 6). With no tool attached, the offset fields show the .rob values read-only; with a tool they and the colour picker edit the attached tool at once.
The tool list (names and STL file names) and each arm's attached tool with its offset and colour are written to the .brw; on import the STLs are read from the workspace folder by name, like the other objects, and re-attached. Keep the tool STL files next to the .brw.
getToolNames() lists the imported tools, attachRobotTool(name, offset, color, robotIdx) mounts one (replacing a mounted tool), detachRobotTool() removes it, setRobotToolOffset() / setRobotToolColor() edit it and getRobotToolInfo() reads it back. A plugin cannot read files, so tools are imported in the Tools window or come with the workspace.
const tools = await brwAPI.getToolNames(); // e.g. ['gripper', 'welder']
await brwAPI.attachRobotTool('welder', [150, 0, 0]); // tool point 150 mm from the flange
// ... run the path ...
await brwAPI.detachRobotTool();
Open view_in_ar Tools & Objects → External Axis. The window works on the active arm and has two panels. The top one is a linear rail: base position (bottom of the rail, at its start, centred on the profile), the translation vector the carriage moves along, and the profile width / height plus the length. Add linear axis generates the rail base and its carriage and puts the arm on the carriage - the arm's base becomes the rail start, one rail height up.
Drag the carriage in the viewport to move it (the arm rides along); it stops at the rail ends (0 … L). Hold SHIFT while dragging to keep the manipulator where it is: the base slides along the rail, but the tool keeps its world position and orientation (the joints are re-solved each step, and the carriage stops where the pose would leave the joint limits or the reach). Each arm can have one rail; Remove axis takes it away and leaves the arm where it stands.
The bottom panel adds a rotary table (external positioner): base position, rotation Rx / Ry / Rz of the table, radius and height. Import workpiece STL puts a part on the turntable, one table height above the base, centred on the rotation axis; the Workpiece row names the STL that sits on the table (or the Mill / Sculpt blank), and the colour picker sets its colour (saved with the workspace). Drag the turntable or the workpiece to turn them together; the table does not move the arm.
Once an axis exists its fields are read-only. The lock chip next to the arm name (as in Joint Control) unlocks them: every change is applied at once - the rail or table is rebuilt, the carriage keeps its travel and the arm stays on it.
When an arm owns an external axis, a saved posture carries the rail travel, the table angle and the arm's base position along with the six joint angles. Playing the queue then moves the rail, the whole arm and the turntable together with the joints: every axis follows a trapezoidal velocity profile over the same number of steps, so they start and stop together, and the velocity setting applies to all of them. The drawn path follows the arm along the rail.
Rails and tables are saved in the workspace (.brw) and rebuilt on import; the workpiece STL is loaded from the workspace folder by name, like the other objects.
addLinearRail() / addRotaryTable() create the axes (a rail has a base position and a rotation rot: [rx, ry, rz], composed Rx·Ry·Rz like the robot base; its travel direction dir is the rotated +X and is returned by getLinearRail(); the robot is mounted on the carriage, so moving or rotating the robot base takes the rail along and rotating the rail turns the robot), setRailPosition() (mm) and setRotaryTableAngle() (degrees) move them, and simulateRobotMotion(jointSteps, robotIdx, railSteps, tableSteps) plays joints, rail and table together - one delta per step, rail in mm, table in degrees, shorter arrays padded with zeros. Postures from getRobotPostureArray() carry [j0..j5, travel, angle (rad), bx, by, bz] and trajectory steps from getRobotTrajData() carry the rail / table deltas as elements 6 and 7 when an axis exists; the workpiece STL can only be attached in the interface.
// rot = [rx, ry, rz] in degrees, or dir: [tx, ty, tz] instead;
// with neither the rail is aligned with the robot base
await brwAPI.addLinearRail({
basePos: [0, 0, 0], rot: [0, 0, 0],
W: 500, H: 300, L: 3000
});
const q0 = await brwAPI.getCurrentJointAngles();
const steps = 100;
const rail = Array(steps).fill(2000 / steps); // 2 m along the rail
const table = Array(steps).fill(90 / steps); // a quarter turn
await brwAPI.simulateRobotMotion([], undefined, rail, table); // joints stay
Robot milling of a blank on the rotary table. The active arm needs a linear rail and a rotary table (Tools & Objects → External Axis). Open carpenter Mill / Sculpt: Import target STL loads the object to be milled (it is centred on the table axis and set on the table top), Calc bounding box fills the box size W, D, H with the object's bounding box (whole millimetres, rounded up; edit the values to leave more material), and Create blank box puts a box of that size on the table as the workpiece. The target must fit inside the blank; the order does not matter, the target's profile is computed whenever both exist. Layer height and angular step are the resolution of the workpiece model; the tool diameter, the depth of cut per pass and the distance the rail keeps between the robot base and the tool line are the milling parameters. Tool approach angle tilts the tool from the horizontal (default 45°): it points at the table axis from above, so the tool body stays outside the material below the tip. Max table speed (°/s, at speed 1) limits the rotary table: near the axis one cell would otherwise pass under the tool in a single step. Creating the blank and importing the target show the progress popup as well.
The workpiece is described as a radius for every angle and height around the table axis; the target object is converted to the same form by rays from the axis, so shapes with undercuts along the radius are milled to their radial silhouette. The tool points at the axis from one direction, tilted down by the approach angle, and the table turns the material under it: one turn mills one layer. Layers go top-down in passes of at most the depth of cut; between two cells the tool moves radially first when the radius grows and turns first when it shrinks, so every cell ends exactly at its target radius. For each layer a few rail positions are tried and the one with the best worst-case Jacobian conditioning inside the joint limits is used; the rail moves with the arm frozen, and the arm then travels to the new clearance pose with a joint move (trapezoidal profile). Steps are spaced by a constant travelled distance derived from the simulation velocity, so the surface speed stays near constant. When the program is ready, the active posture of the arm gets the Milling Program movement type with it (a queue without postures first gets the arm's current pose as posture 1).
Generate milling program shows a progress popup that advances one layer at a time and has a Cancel button. Start simulation remembers the arm, rail and table state of the first run (the pre-milling state); every run first returns there with a free move (trapezoidal velocity profile, the table by its shortest turn) - a run started again after Stop comes back from wherever it was interrupted - then moves to the program's first state and plays the program like Play Full Sequence, moving arm, rail and table together; the workpiece mesh is carved live, step by step, so at the end it is the target object. The generated program can also be the move of a posture in the queue: choose Milling Program as its movement type in Posture Info (see Interactive Controls → Movement Types). Layers are milled top-down, so at first only the top of the blank changes. Speed is the number of planned steps played per frame (1 keeps the planned surface speed; 5 or more makes the carving visible at a glance). The pause button pauses and resumes, stop ends the run, and the two arrows jump 10 % of the program backwards or forwards: the arm, rail and table are put into the state of that step and the workpiece is rebuilt from the blank up to it. The status line shows the step, the percentage and the share of the blank already removed. Programs are long (a 200 mm box to a 100 mm object is about a hundred thousand steps), so the path line is not drawn for them.
The tool cuts one layer per pass with a footprint half a diameter to each side, so features narrower than the tool are rounded; undercuts along the radius cannot be produced. The blank exists only in memory: the name it shows (blank_W x D x H.stl) is the file name the workspace refers to. A workspace export writes the untouched blank under that name (whatever has been milled since) and the target STL next to it, and the table config keeps the blank's parameters; importing that folder restores the Mill / Sculpt window with the blank, the box fields and the target, so only the program has to be generated again (it is not saved, see Movement Types). Export workpiece STL downloads the workpiece as it is now: the blank under its name, or a milled state as milled_<name>. The rotary table's angle keeps counting turns during a program.
Tools & Objects → Collision Avoidance checks the moves of the active robot's posture queue against the objects in the workspace and, when a move comes too close to one, recalculates it so that the arm passes around the object. The check runs on the same trajectory steps Play uses, so Line, Arc and 3D Curve moves are tested along their real path.
Recalculate trajectory runs the check and the recalculation (a progress window shows the move being worked on and can cancel it). Back to original trajectory restores the postures and movement types as they were before the first recalculation. After a check, the colliding steps of every played trajectory are drawn red on the path.
The arm is modelled with capsules fitted to the mesh of every moving link: the link is cut into slices along its main direction and each slice gets a capsule of its own radius, so a link that is thick at the joint and slim along the arm is followed closely (an attached tool included). An object is its oriented bounding box, refined by the surface points when the precision factor is above the minimum. For every tested pose the distance from each capsule to each object is compared with the threshold.
A colliding move is replanned in joint space with a bidirectional rapidly-exploring random tree (RRT-Connect): two search trees grow from the two postures towards each other, only through configurations that are inside the joint limits and clear of the objects. The found path is then shortened to a few via configurations, which become the new postures or the curve points. The result is accepted only when the real trajectory steps pass the check; otherwise the search is repeated with a larger reserve.
setCollisionAvoidanceParams(params) sets the settings like the window's controls, only the given ones: { avoidAll, objects, detectOnly, threshold, precision, method: 'curve' | 'postures', avoidFloor, floorZ }, and returns them as they are afterwards. setCollisionAvoidanceObjects([indices]) chooses the objects to avoid when not all are. recalculateCollisionAvoidance(robotIdx) runs the check and the recalculation and resolves with { ok, collisions, recalculated, unsolved, messages, error }; restoreOriginalTrajectory(robotIdx) brings the original queue back.
await brwAPI.setCollisionAvoidanceParams({ threshold: 20, precision: 0, method: 'postures' });
await brwAPI.setCollisionAvoidanceObjects([0]); // only object 0
const r = await brwAPI.recalculateCollisionAvoidance();
brwAPI.logMessage(`${r.collisions} colliding move(s), ${r.recalculated} recalculated`);
// await brwAPI.restoreOriginalTrajectory(); // undo
The check covers the arm against workspace objects and, when checked, the floor plane. It does not cover the arm against itself, other robot arms, the rail and table parts, or an object while it is carried. A posture of the queue may itself be closer to an object than the threshold, an approach pose beside a part for example: it is kept and reported, and the moves leave and reach it through a zone in which the required clearance grows from the posture's own clearance to the full threshold. Only a posture that touches an object cannot be used: move it or deselect the object. Insert Curve Points can fail where the tool path alone cannot keep the whole arm clear; Insert New Postures is the more reliable method. Inserted postures use free moves, so a Line or Arc move that collided becomes a sequence of free moves.
Plugins let you drive the arm from your own JavaScript instead of clicking through the interface. Use them to generate toolpaths, batch postures, or run a whole routine unattended. Your code runs in a sandboxed Web Worker, so it has no access to the page; it reaches the application only through the injected brwAPI object.
brw-plugin.js in a folder, on its own or beside a .brw workspace.userCommand(1), (2) or (3).A workspace is optional. If you only want the plugin commands, a folder
holding nothing but brw-plugin.js loads fine. Selecting a folder with
neither file shows a message instead.
Your file must define userCommand. The argument tells you which button was pressed, so a single plugin can carry three separate routines:
async function userCommand(cmdId)
{
brwAPI.logMessage('Plugin started');
const q0 = await brwAPI.getCurrentJointAngles();
const start = await brwAPI.getCurrentWorldPosition();
const vel = await brwAPI.getSimulationVelocity();
if (cmdId == 1)
{
// ...build a path, then play it back:
await brwAPI.simulateRobotMotion(simDataArr);
}
}
Every brwAPI call is asynchronous. Each one is forwarded to the application and resolved back to the worker, so it must be awaited.
Install the BabaCAD Robotics VS Code extension for API auto-complete.
Pressing a command button runs your userCommand straight away,
against the arm as it currently stands. Here command 1 traces a heart at
the tool, with the console reporting each stage as it goes.
Nothing is queued into the posture list, so a plugin can be re-run as often as you like while you tune it.
Robot-related calls take an optional trailing robotIdx (the arm's slot index, 0-based); when omitted they act on the active arm. World positions and normals are absolute and include the arm's base position and rotation; addRobotArm(modelIdx, basePos, baseRot), getRobotBaseRot() and setRobotBaseRot() use [rx, ry, rz] in degrees. The ten *Robot* calls after getCurrentNormalVector() manage the arms themselves: count, active arm, model, add / remove and base position.
logMessage()
worldToJointAngles()
worldToJointAnglesBatch()
findTrajSingularity()
jointAnglesToWorld()
jointAnglesToWorldBatch()
getCurrentJointAngles()
getCurrentWorldPosition()
getCurrentNormalVector()
getRobotCount()
getActiveRobotIdx()
setActiveRobotIdx()
getRobotModelNames()
getRobotModelIdx()
getRobotConfigType()
setRobotModelIdx()
addRobotArm()
removeRobotArm()
getRobotBasePos()
setRobotBasePos()
getRobotBaseRot()
setRobotBaseRot()
getLinearRail()
addLinearRail()
removeLinearRail()
getRailPosition()
setRailPosition()
getRotaryTable()
addRotaryTable()
removeRotaryTable()
getRotaryTableAngle()
setRotaryTableAngle()
getToolNames()
getRobotToolInfo()
attachRobotTool()
detachRobotTool()
setRobotToolOffset()
setRobotToolColor()
getTrajLineStyle()
setTrajLineStyle()
setTrajStepStyle()
getWorkspaceObjectPos()
setWorkspaceObjectPos()
rotateWorkspaceObject()
getWorkspaceObjectRotationAngles()
setWorkspaceObjectRotationAngles()
getWorkspaceObjectTransformMatrix()
setWorkspaceObjectTransformMatrix()
getWorkspaceObjectNormal()
setWorkspaceObjectNormal()
findWorkspaceObjectsByName()
getWorkspaceObjectName()
getWorkspaceObjectCount()
getWorkspaceObjectBoundingBox()
setCollisionAvoidanceParams()
setCollisionAvoidanceObjects()
recalculateCollisionAvoidance()
restoreOriginalTrajectory()
pickWorkspaceObject()
dropWorkspaceObject()
addRobotPosture()
removeRobotPosture()
updateRobotPosture()
getPostureMoveType()
setPostureMoveType()
getMillingProgram()
simulateRobotMotion()
getRobotJointLimits()
isPostureValid()
getJointAnglesMapping()
setJointAnglesMapping()
getHomeJointAngles()
getRobotTrajData()
getRobotPostureArray()
getRobotCurrentPostureIdx()
getSimulationVelocity()
sendDataToSerialPort()
A complete worked example ships as plugins/brw-plugin.js. Commands 1 and 2 draw rectangles and circles in multiple passes down the Z-axis (with the singular poses of the path marked on the line), command 3 takes the milling program generated in Mill / Sculpt with getMillingProgram() and adds a posture that runs it (MoveType.MILL), and userAction() shows how a posture's plugin action receives the G-code text.
BabaCAD Robotics Web is one of the first industrial robot simulation and programming applications with artificial intelligence built in. The AI Assistant sits beside the 3D workspace: you describe in plain language what the robot should do, or ask how something in the application works, and it answers with an explanation or with a ready program for the simulator.
brw-plugin.js, loaded and run once: it defines the postures and movement types in the queue, and you press Play to watch the robot do it.Type /demo to try a ready-made request, and /repeat to send the last request again.
The assistant is used with an activation code, and every code carries a balance of tokens, the AI credits. Each request spends tokens: your question, the knowledge sent with it and the answer all count, so a task that needs the API functions costs more than a short how-to question. The panel shows the tokens left and the cost of the last request; when the balance is used up, the assistant stops answering until the code is topped up.
The AI Assistant is in an experimental phase and currently available to educational institutions, mainly teachers and professors. To get an activation code with credits, or more credits for a code you have, click Activate... and then Request Activation Code, or write directly to support@babacad.com and tell us who you are and where you teach.
Requests are paced (a short pause between two requests and a limit per hour), and questions are limited in length. Violent or spam requests and bot-like request rates put a code on a blacklist, after which it cannot be activated. With your consent, given in the window shown after activation, your questions and ratings are collected to improve the assistant; without it they stay in your browser.
The AI Assistant is docked at the right edge of the workspace. Click its header or the arrow to collapse it to the title bar, and the ✕ to hide it. Settings → Show AI Assistant shows or hides the panel (the ✕ unchecks it). By default the panel is visible but collapsed to its title bar; afterwards it remembers the state you left it in.
Activate... at the top of the panel opens a window for your activation code. The code is sent with every request and the service checks it and counts the tokens used against it. It is kept for the current session only, unless Remember on this device is checked (then it is stored in this browser); Deactivate forgets it. Once the code is accepted a window suggests starting with the /demo prompt, a ready-made task request (type /demo and send it, or use its button); /repeat sends the last request again. Nothing is sent before a code is entered. Abusive use (violent or spam requests, bot-like request rates) puts a code on a blacklist: such a code cannot be activated, and a window names the reason and the support address. Request Activation Code, under the code field in that window, explains how to get one: the AI Assistant is an experimental feature, available to educational institutions (mainly teachers and professors), and a code is requested by email from support@babacad.com. Under the button the panel shows the tokens left for your code and the cost of the last request; the same line is in the activation window. The numbers come from the service with every answer, so they appear after the first request.
Type a question and press Enter or the send button (Shift+Enter starts a new line). The answer appears in the panel below your question; errors from the service, such as a wrong code or used-up tokens, are shown there as well. Code in an answer is shown in a box with a Copy button. Under each answer the panel asks, optionally, how the assistant is doing: 1 Bad, 2 Fine, 3 Good or 0 Dismiss (buttons, or the digit typed into the prompt; Dismiss hides the question for the session). Ratings stay in your browser unless you have allowed their collection: the window shown after activation asks whether your questions and ratings may be collected to improve the assistant, with a checkbox (unchecked by default, changeable any time through Activate...); only with it checked are ratings also sent to BabaCAD. An answer rated Good is remembered with its request, and when a later task resembles it, that answer is shown to the model as the pattern to follow, which makes answers to recurring tasks more consistent.
With every question the assistant reads three files and sends what the question needs to the model as its system prompt, in English: this documentation as the Markdown file docu/brw-help-ai.txt (made from this page by scripts/help2text.py, which drops the sections the assistant does not need, such as this one; brw-help.txt is the full page, and the page itself is converted in the browser when neither file is there), the plugin API functions with their syntax (plugins/apiFunctions.js) and the demo plugin (plugins/brw-plugin.js). A task (writing a plugin, making the robot do something) gets all API functions with their syntax and the demo plugin, plus the parameter descriptions of the functions the task touches; every question gets the documentation sections that match its words, and a how-to question that matches nothing (one not asked in English, for example) gets the whole documentation. It can therefore explain the application and write plugins: generated code uses the API syntax, follows the structure of the demo plugin and comes as JavaScript inside a code block. The panel says what was sent and how large it is, since it counts towards the tokens of the request. If the model's context window is too small for all of it, the assistant says so and asks again with the part the question needs: the API functions and the demo plugin for a task, the documentation for a how-to question.
When an answer contains code for a task (for example "move the robot from the initial pose to [0, 1000, 0] along a line"), the panel asks whether to save it as brw-plugin.js and run it. Save and run writes the file, loads it as the application's plugin and runs the code once: that defines the task (the postures in the queue), and the panel answers Task is defined, just click the Play >> button to run the simulation. When you ask for a plugin or a user command explicitly, the panel instead asks which user command (1, 2 or 3) should run the code; Save and load plugin writes and loads the file, and clicking that user command runs it. The file goes into the workspace folder: after an export to a folder in this session it is written there directly; after an import the browser asks you to choose the workspace folder and to allow writing (a web page cannot write into a folder on its own); without a workspace you are asked for any folder. Browsers without folder access (Firefox, Safari) download the file instead and the plugin is loaded all the same. A plugin that was already loaded is kept: its commands that are not reassigned still run, and each saved task replaces only its own command. Code that does not compile is not saved. A workspace export now includes the loaded brw-plugin.js.
Anything a plugin passes to logMessage() is printed to the console docked along the bottom of the workspace, so you can follow a run without opening the browser's developer tools.
brwAPI.logMessage('Pass ' + i + ' of 4 complete');
Objects and arrays are formatted as JSON, everything else is printed as text. Each line is timestamped and the view follows the newest entry.
The console is always docked and starts collapsed to its title bar. Click the bar, or the chevron, to expand it.
While collapsed, incoming messages raise a counter on the title bar rather than opening the panel, so a running plugin never pulls focus from the scene. The most recent 500 lines are kept.
A plugin can push data out over a serial connection while the simulation runs, so the same routine that moves the arm on screen can drive a real controller, a gripper, or any device listening on the port.
Open settings Settings and press Serial Configure to choose a port. Until a port is connected nothing is transmitted, and calls to the API are simply ignored.
One call does the work. It takes a string, so format the payload however the device on the other end expects, including any line terminator.
// stream the live joint angles out as degrees
const q = await brwAPI.getCurrentJointAngles();
const deg = q.map(a => (a * 180 / Math.PI).toFixed(2));
await brwAPI.sendDataToSerialPort(`J ${deg.join(' ')}
`);
Combined with simulateRobotMotion, this lets a plugin play a path in
the viewport and mirror it to hardware in the same pass.
BabaCAD Robotics supports a wide variety of localizations, accessible via the language icon:
Velocity: Adjust movement speed slider.
Show Pivot/Path: Toggle visualization of the robot's gizmo and trajectory path.
Dark Mode: Switches the whole interface between the light and dark themes. Your choice is remembered on this machine and restored on the next visit.
Center the view: Resets the view center (to the origin for one arm, to the middle of the arms for several) and the zoom to the robot model's defaults, after panning with Shift / Alt + drag or zooming with the wheel.
Info: Access system versioning and the Discord Link for support.