Add a 3D model
8 minute read
The last step gives your dashboard a digital twin: a 3D model of your robot or machine in the Grid 3D widget, with its parts moved by parameters from your application. The guide below is the 3D models page of the Grid documentation, shown here so you can finish the walkthrough without leaving it.
The Grid 3D widget shows a 3D model, a digital twin, of your robot or machine, and moves its parts with live parameters from your application: a joint turns when the real joint turns. You build the model in Blender or a CAD tool, export it as a glTF file, and write a small JSON file that links each moving part to a parameter.
The video shows the whole workflow, from creating a model to linking it to an application:
Prepare the model
The widget moves an object by setting its position, rotation or scale relative to its parent. So how you build the model decides how it moves:
- Build with z up. The camera treats z as up. In Blender’s glTF exporter, switch +Y Up off under Transform.
- Put each moving part’s origin at its pivot. In Blender, place the 3D cursor on the joint and use Object > Set Origin > Origin to 3D Cursor.
- Parent the parts in a chain, base to tool, so each part moves with the one before it.
- Apply rotation and scale to every moving part (Object > Apply > Rotation & Scale), so its local axes line up with the joint.
- Give moving parts simple names, without spaces, dots or other special characters. See the name rules below.
- Export as
.glb, the binary format. Tick Apply Modifiers under Geometry if you use modifiers. Animations you made in Blender are exported with the model and can be played in the dashboard.
Keep the file small, so the dashboard loads quickly on a tablet: the stock robot models are 4 to 8 MB. There is no hard limit. Draco and Meshopt compression make files smaller and are supported. A .gltf text file also works, as long as its buffers and textures are embedded in it.
How object names change on import
The importer changes some characters in object names. Use the changed name in the JSON file:
| In Blender | In the widget | Example |
|---|---|---|
| A space | Becomes _ |
Upper Arm becomes Upper_Arm |
. : / [ ] |
Removed | Cube.001 becomes Cube001 |
| The same name twice in one file | _1, _2 and so on are added |
The second Link becomes Link_1 |
To see the final names, type grid3d.tree() in the browser console. See Inspect the scene in the browser console.
Write the model JSON
The JSON file tells the widget which model files to load and which parameter moves which object. The paths of model files are relative to the JSON file. Name the file <name>.3d.json, for example robot.3d.json: the file editor in the portal then checks it against the schema and suggests the keys as you type.
This minimal file loads one model and turns the object Link1 with the first joint position:
{
"files": [
{ "file": "robot.glb" }
],
"objects": [
{
"name": "Link1",
"links": [
{ "link": "root/ManipulatorControl/jointPositionsActual", "channel": 0, "axis": "rz" }
]
}
]
}
Each entry in links takes one value from a parameter and applies it to the object. These are the fields you need most:
| Field | What it does | Default |
|---|---|---|
link |
The parameter path | |
channel |
The element of an array parameter. Ignored for a single value | 0 |
axis |
What the value sets, see the next table | |
gain, offset |
The widget uses value × gain + offset |
1, 0 |
value |
A fixed value, used while the parameter has no data, or always when there is no link |
axis |
Sets | Values read |
|---|---|---|
x, y, z |
Position along one local axis, in m | 1 |
rx, ry, rz |
Rotation about one local axis, in rad | 1 |
position |
Position x, y, z | 3, from channel on |
rotationZYX |
Rotation z, y, x | 3, from channel on |
pose |
Position x, y, z, then rotation z, y, x: a pose such as a tool pose | 6, from channel on |
s, sx, sy, sz |
Scale: all axes, or one | 1 |
visible |
Shows the object when the value is 0.5 or more |
1 |
opacity |
Transparency, from 0 to 1 |
1 |
The widget does not convert units: positions are in metres and rotations in radians, relative to the object’s parent. For a parameter in degrees, use gain 0.0174533; for millimetres, 0.001. Colours, glow, any other property of an object, vector lengths and numbered objects are on Models and objects.
Example: the joints and tool frame of a 6-axis arm
Every joint is linked the same way, to its element of the joint positions. The tool frame follows the tool pose:
"objects": [
{
"name": "Link1",
"castShadow": true,
"links": [{ "axis": "rz", "link": "root/ManipulatorControl/jointPositionsActual", "channel": 0 }]
},
{
"name": "Link2",
"castShadow": true,
"links": [{ "axis": "ry", "link": "root/ManipulatorControl/jointPositionsActual", "channel": 1 }]
},
{
"name": "ToolPose",
"links": [{ "axis": "pose", "link": "root/ManipulatorControl/manipulatorToolPoseActual" }]
}
]
Older files put link, channel and axis directly on the object, for a single link. The widget still reads that form, but new files use links.
To find an object inside another one, separate the names with *: "Robot2*Link1" finds Link1 under Robot2. Use this when you load two models with the same object names. Give each one its own name in files, because duplicates across files are not renamed.
All options at a glance
The same file can do much more than move parts. Each topic has its own page:
| Key | What it does | Details |
|---|---|---|
files |
The model files to load, how often and where: one copy, several named copies, or thousands of copies drawn with GPU instancing | Models and objects |
objects |
Settings and links per object: shadows, and parameters that move, show, colour or change it | Models and objects |
animations |
Plays the animations in a model: by itself, on a trigger, at a linked speed, or at a linked position | Animations |
traceLine |
Draws the path of a point, such as the tool centre, coloured or thickened by a value such as the speed | Trace lines |
camera, cameraPosition, cameraLookAt |
Where you look from and at | Camera, lights and rendering |
hemisphereLight, spotLight |
The lights, and the shadows of the spot light | Camera, lights and rendering |
renderSettings |
Frame rate, lighting model, environment, background, ambient occlusion, bloom | Camera, lights and rendering |
Two rules apply to the whole file:
- A key that ends in
_is ignored. JSON has no comments, so use this to keep an alternative setting:"spotLight_": { ... }switches the spot light off and keeps its settings, and afilesentry with"file_"instead of"file"is not loaded. - Old keys keep working. Settings the schema marks as deprecated are still read, so files made for earlier versions show the same scene. The editor marks them, with what to use instead.
The schema
The schema describes every key, its type and its default:
v-3d schema. The portal’s file editor uses it for files named *.3d.json. Other editors, such as Visual Studio Code, can use it through their JSON schema settings.
Add it to the dashboard
- Upload the model files and the JSON file to your Grid project, in the same folder or with matching relative paths.
- Add a 3D widget to your canvas.
- In its model property, select the JSON file.
The widget has two more settings: frequency divider, how often the parameters update (default 100), and disable view navigation, which locks the camera.
When something is wrong
While the widget loads the file, it checks it: files that are missing or are not a glTF model, objects that are not found, keys with a value it does not know. Errors and warnings show in a pop-up on the dashboard, and all of them, with the details, in the browser console (press F12). Each message names the place in the file, such as objects[3] or animations[0], and most say how to fix it. For an object that is not found, the message lists the names that come close.
Inspect the scene in the browser console
Open the browser console (F12) on the dashboard and type one of these:
| Command | Shows |
|---|---|
grid3d.tree() |
The object hierarchy, names only, including empties: the names to use in objects and parent. grid3d.tree('Robot') shows only the branch under Robot |
grid3d.find('Link1') |
The object with that name, to look at its position, rotation and materials |
grid3d.animations() |
The animations each model can play, their length, and which entry plays them now. grid3d.animations('Conveyor') shows one model |
grid3d.scene |
The whole scene of the 3D widget on screen |
grid3d.views |
All 3D widgets on screen, by JSON file, each with its scene, camera and renderer |
The console also shows the Grid version the dashboard runs, in the line that starts with grid version:.
Things to know
- Materials are shared.
opacitychanges the object’s material, and every other object in the same file that uses that material changes with it. Give an object its own material before you make it transparent. Copies of a model from anamelist or aninstanceslist each get their own materials. - Shadows cost computing power. Objects cast no shadows unless you set
castShadoworreceiveShadowon them. Keep these to a few objects on slower devices. - Only changed values are applied, so a parameter that does not change does not cost anything. The scene is only drawn again when something changed, or while an animation plays.
Next: Step 7: final check, to test every step.