MeshCat
October 16, 2025 ยท View on GitHub
MeshCat is a remotely-controllable 3D viewer, built on top of three.js. The MeshCat viewer runs in a browser and listens for geometry commands over WebSockets. This makes it easy to create a tree of objects and transformations by sending the appropriate commands over the websocket.
The MeshCat viewer is meant to be combined with an interface in the language of your choice. Current interfaces are:
API
MeshCat can be used programmatically from JS or over a WebSocket connection.
Programmatic API
To create a new MeshCat viewer, use the Viewer constructor:
let viewer = new MeshCat.Viewer(dom_element);
where dom_element is the div in which the viewer should live. The primary interface to the viewer is the handle_command function, which maps directly to the behaviors available over the WebSocket.
Viewer.handle_command(cmd)-
Handle a single command and update the viewer.
cmdshould be a JS object with at least the fieldtype. Available command types are:set_object-
Set the 3D object at a given path in the scene tree from its JSON description. Any transforms previously applied to that path will be preserved and any children of that path will continue to exist. To remove transforms and delete all children from a given path, you should send a
deletecommand first (see below).Internally, we append a final path segment,
<object>, to the provided path before creating the object. This is done because clients may disagree about what the "intrinsic" transform of a particular geometry is (for example, is a "Box" centered on the origin, or does it have one corner at the origin?). Clients can use thematrixfield of the JSON object to store that intrinsic transform, and that matrix will be preserved by attaching it to the<object>path. Generally, you shouldn't need to worry about this: if you set an object at the path/meshcat/foo, then you can set the transform at/meshcat/fooand everything will just work.Additional fields:
path- A
"/"-separated string indicating the object's path in the scene tree. An object at path"/foo/bar"is a child of an object at path"/foo", so setting the transform of (or deleting)"/foo"will also affect its children. object- The Three.js Object, with its geometry and material, in JSON form as a JS object. The nominal format accepted is anything that ObjectLoader can handle (i.e., anything you might get by calling the
toJSON()method of a Three.js Object3D).Beyond the nominal format, Meshcat also offers a few extensions for convenience:
- Within the
geometriesstanza, thetypefield can be set to"_meshfile_geometry"to parse using a mesh file format. In this case, the geometry must also have a field namedformatset to one of"obj"or"dae"or"stl"and a field named"data"with the string contents of the file. - Within the
geometriesstanza, thetypefield can be set to"LineGeometry"to create lines with configurable width (works in all modern browsers). See the Line2 example below for details. - Within the
materialsstanza, thetypefield can be set to"_text"to use a string as the texture (i.e., a font rendered onto an image). In this case, the material must also have fields namedfont_size(in pixels),font_face(a string), andtext(the words to render into a texture). - Within the
materialsstanza, thetypefield can be set to"LineMaterial"to create materials for Line2 objects with configurable line width. See the Line2 example below for details. - Within the inner
objectstanza (i.e., the object with a uuid, not the object argument to set_object), thetypefield can be set to"_meshfile_object"to parse using a mesh file format. In this case, thegeometriesandmaterialsandgeometry: {uuid}andmaterial: {uuid}are all ignored, and the object must have a field namedformatset to one of"obj"or"dae"or"stl"and a field named"data"with the string contents of the file. When the format is obj, the object may also have a field namedmtl_librarywith the string contents of the associated mtl file. - Within the inner
objectstanza, thetypefield can be set to"Line2"to render lines with configurable width using LineGeometry and LineMaterial. See the Line2 example below for details.
- Within the
Example (nominal format):
{ type: "set_object", path: "/meshcat/boxes/box1", object: { metadata: {version: 4.5, type: "Object"}, geometries: [ { uuid: "cef79e52-526d-4263-b595-04fa2705974e", type: "BoxGeometry", width: 1, height: 1, depth:1 } ], materials: [ { uuid: "0767ae32-eb34-450c-b65f-3ae57a1102c3", type: "MeshLambertMaterial", color: 16777215, emissive: 0, side: 2, depthFunc: 3, depthTest: true, depthWrite: true } ], object: { uuid: "00c2baef-9600-4c6b-b88d-7e82c40e004f", type: "Mesh", geometry: "cef79e52-526d-4263-b595-04fa2705974e", material: "0767ae32-eb34-450c-b65f-3ae57a1102c3" } } }Note the somewhat indirect way in which geometries and materials are specified. Each Three.js serialized object has a list of geometries and a list of materials, each with a UUID. The actual geometry and material for a given object are simply references to those existing UUIDs. This enables easy re-use of geometries between objects in Three.js, although we don't really rely on that in MeshCat. Some information about the JSON object format can be found on the Three.js wiki.Example (
_meshfile_geometry):{ type: "set_object", path: "/some/file/geometry", object: { metadata: { version: 4.5, type: "Object" }, geometries: [ { type: "_meshfile_geometry", uuid: "4a08da6b-bbc6-11ee-b7a2-4b79088b524d", format: "obj", data: "v -0.06470900 ..." } ], images: [ { uuid: "c448fc3a-bbc6-11ee-b7a2-4b79088b524d", url: "data:image/png;base64,iVBORw0KGgoAAA==" } ], textures: [ { uuid: "d442ea92-bbc6-11ee-b7a2-4b79088b524d", wrap: [1001, 1001], repeat: [1, 1], image: "c448fc3a-bbc6-11ee-b7a2-4b79088b524d" } ], materials: [ { uuid: "4a08da6e-bbc6-11ee-b7a2-4b79088b524d", type: "MeshLambertMaterial", color: 16777215, reflectivity: 0.5, map: "d442ea92-bbc6-11ee-b7a2-4b79088b524d" } ], object: { uuid: "4a08da6f-bbc6-11ee-b7a2-4b79088b524d", type: "Mesh", geometry: "4a08da6b-bbc6-11ee-b7a2-4b79088b524d", material: "4a08da6e-bbc6-11ee-b7a2-4b79088b524d" } } }Example (
_text):{ type: "set_object", path: "/meshcat/text", object: { metadata: { version: 4.5, type: "Object" }, geometries: [ { uuid: "6fe70119-bba7-11ee-b7a2-4b79088b524d", type: "PlaneGeometry", width: 8, height: 8, widthSegments: 1, heightSegments: 1 } ], textures: [ { uuid: "0c8c99a8-bba8-11ee-b7a2-4b79088b524d", type: "_text", text: "Hello, world!", font_size: 300, font_face: "sans-serif" } ], materials: [ { uuid: "6fe7011b-bba7-11ee-b7a2-4b79088b524d", type: "MeshPhongMaterial", transparent: true, map: "0c8c99a8-bba8-11ee-b7a2-4b79088b524d", } ], object: { uuid: "6fe7011c-bba7-11ee-b7a2-4b79088b524d", type: "Mesh", geometry: "6fe70119-bba7-11ee-b7a2-4b79088b524d", material: "6fe7011b-bba7-11ee-b7a2-4b79088b524d", } } }Example (
_meshfile_object):{ type: "set_object", path: "/meshcat/wavefront_file", object: { metadata: {version: 4.5, type: "Object"}, object: { uuid: "00c2baef-9600-4c6b-b88d-7e82c40e004f", type: "_meshfile_object", format: "obj", data: "mtllib ./cube.mtl\nusemtl material_0\nv 0.0 0.0 0.0 ...", mtl_library: "newmtl material_0\nKa 0.2 0.2 0.2\n ...", resources: {"cube.png": "data:image/png;base64,iV ..."} } } }Check
test/meshfile_object_obj.htmlfor the full demo.Example (Line2 with
LineGeometryandLineMaterial):Modern browsers don't support the GL_LINEWIDTH parameter, so standard Line objects won't show varying line widths. MeshCat supports Line2 objects with LineGeometry and LineMaterial for lines with configurable properties.
{ type: "set_object", path: "/meshcat/fatline", object: { metadata: {version: 4.5, type: "Object"}, geometries: [ { uuid: "6fe70119-bba7-11ee-b7a2-4b79088b524d", type: "LineGeometry", position: { array: new Float32Array([0, 0, 0, 1, 0, 0, 1, 1, 0]) }, color: { array: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) } } ], materials: [ { uuid: "6fe7011b-bba7-11ee-b7a2-4b79088b524d", type: "LineMaterial", color: 16777215, linewidth: 5.0, vertexColors: true, dashed: false, worldUnits: false } ], object: { uuid: "6fe7011c-bba7-11ee-b7a2-4b79088b524d", type: "Line2", geometry: "6fe70119-bba7-11ee-b7a2-4b79088b524d", material: "6fe7011b-bba7-11ee-b7a2-4b79088b524d" } } }LineGeometry supports these attributes:
position: Float32Array of vertex positions (x, y, z, x, y, z, ...)color: (optional) Float32Array of per-vertex RGB colors (r, g, b, r, g, b, ...)
LineMaterial supports the exact same attributes as documented in the Three.js LineMaterial docs.
Check
test/fatlines.htmlfor a full demo. set_transform-
Set the homogeneous transform for a given path in the scene tree. An object's pose is the concatenation of all of the transforms along its path, so setting the transform of
"/foo"will move the objects at"/foo/box1"and"/foo/robots/HAL9000".Additional fields:
path- A
"/"-separated string indicating the object's path in the scene tree. An object at path"/foo/bar"is a child of an object at path"/foo", so setting the transform of (or deleting)"/foo"will also affect its children. matrix-
The homogeneous transformation matrix, given as a 16-element
Float32Arrayin column-major order.
{ type: "set_transform", path: "/meshcat/boxes", matrix: new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.5, 0.0, 0.5, 1]) } delete-
Delete the object at the given path as well as all of its children.
Additional fields:
path- A
"/"-separated string indicating the object's path in the scene tree. An object at path"/foo/bar"is a child of an object at path"/foo", so setting the transform of (or deleting)"/foo"will also affect its children.
{ type: "delete", path: "/meshcat/boxes" } set_property-
Set a single named property of the object at the given path. If no object exists at that path, an empty one is automatically created.
Note: as we append an extra path element with the name
<object>to every item created withset_object, if you want to modify a property of the object itself, rather than the group containing it, you should ensure that your path is of the form/meshcat/foo/<object>Additional fields:
property-
The name of the property to set, as a string. The following properties are convenience properties. Meshcat provides a mapping from these *names* to various properties contained throughout its scene graph:
visible: boolposition: number[3]quaternion: number[4]scale: number[3]color: number[4]opacity: number(this is the same as the 4th element ofcolor)modulated_opacity: numbertop_color: number[3](only for the Background)bottom_color: number[3](only for the Background)
THREE.Object3Dobject. This provides a powerful capability to customize the scene, but should be considered an advanced usage -- you're on your own to avoid any unwanted side-effects.Properties can be *chained*. For example, for an object with a phong material (MeshPhongMaterial), we may want to tweak its shininess, making it duller. Shininess is not a property of the object itself, but the object's material. There is no *path* to that material, but the property name can include a property name chain, e.g., `material.shininess`. While setting the property, the chained properties will be evaluated in sequence, such that the final name in the chain is the property that receives the `value`.
As noted, specifying a `path` that doesn't exist creates that path. However, specifying a property that doesn't exist does *not* create that property. If a name in the property chain is missing, an error message will be printed to the console and no value will be assigned. This is not a no-op per se. If the `path` led to the implicit creation of a new folder and object, that pair will still be in place.
More subtly, if the property name chain has an interior name (e.g., the `foo` in `material.foo.color`) that exists but is not an object and does not have properties (such as if `foo` were a `Number`), then, again, an error gets written to the console and no value will be assigned.
Finally, property chains can include arrays, such as `"children[1].material.specular"`. The index will be evaluated as a property (with all of the potential consequences as outlined above). In error messages, it may be reported as `children.1` instead of `children[1]`.
value- The new value.
{ type: "set_property", path: "/Cameras/default/rotated/<object>", property: "zoom", value: 2.0 }Example 2:{ type: "set_property", path: "/Lights/DirectionalLight/<object>", property: "intensity", value: 1.0 }Example 3 (chained properties):{ type: "set_property", path: "/Lights/SpotLight/<object>", property: "shadow.radius", value: 1.0 } set_animation-
Create an animation of any number of properties on any number of objects in the scene tree, and optionally start playing that animation.
Additional fields:
animations-
A list of objects, each with two fields:
path-
The path to the object whose property is begin animated. As with
set_propertyabove, you will need to append<object>to the path to set an object's intrinsic property. clip-
A Three.js
AnimationClipin JSON form. The clip in turn has the following fields:fps- The frame rate of the clip
name- A name for this clip. Not currently used.
tracks-
The tracks (i.e. the properties to animate for this particular object. In Three.js, it is possible for a track to specify the name of the object it is attached to, and Three.js will automatically perform a depth-first search for a child object with that name. We choose to ignore that feature, since MeshCat already has unambiguous paths. So each track should just specify a property in its
namefield, with a single"."before that property name to signify that it applies to exactly the object given by thepathabove.Each track has the following fields:
name-
The property to be animated, with a leading
"."(e.g.".position") type-
The Three.js data type of the property being animated (e.g.
"vector3"for thepositionproperty) keys-
The keyframes of the animation. The format is a list of objects, each with a field
time(in frames) andvalueindicating the value of the animated property at that time.
options-
Additional options controlling the animation. Currently supported values are:
play- Boolean [true]. Controls whether the animation should play immediately.
repetitions- Integer [1]. Controls the number of repetitions of the animation each time you play it.
{ type: "set_animation", animations: [{ path: "/Cameras/default", clip: { fps: 30, name: "default", tracks: [{ name: ".position" type: "vector3", keys: [{ time: 0, value: [0, 1, .3] },{ time: 80, value: [0, 1, 2] }], }] } },{ path: "/meshcat/boxes", clip: { fps: 30, name: "default", tracks: [{ name: ".position" type: "vector3", keys: [{ time: 0, value: [0, 1, 0] },{ time: 80, value: [0, -1, 0] }], }] } }], options: { play: true, repetitions: 1 } } set_target-
Set the target of the 3D camera, around which it rotates. This is expressed in a left-handed coordinate system where
y is up.Example:
{ "type": "set_target", value: [0., 1., 0.] }This sets the camera target to `(0, 1, 0)` capture_image-
Capture an image from the viewport. At the moment it will return the image at a provided resolution (by default 1920x1080).
{ "type": "capture_image", "xres": 1920, "yres": 1080 }This sets the camera target to `(0, 1, 0)` set_render_callback-
Each render loop updates the camera, renders the scene and updates
animation. Between updating the camera and rendering the scene,
the Viewer invokes a user-configurable callback. This callback
can be used to execute arbitrary code per rendered frame.
The callback is evaluated in a scope such that the Viewer instance is available as `this`. In declaring the command, the callback should be a *string* that gets evaluated into a function. Passing the string "null" will restore the render callback to being its default no-op function.
For example, the following is an example callback that dispatches the camera's pose in the world to the open websocket connection.{ "type": "set_render_callback", "callback": `() => { if (this.is_perspective()) { if (this.connection.readyState == 1 /* OPEN */) { this.connection.send(msgpack.encode({ 'type': 'camera_pose', 'camera_pose': this.camera.matrixWorld.elements })); } } }` }This dispatches the camera's pose in the world to the websocket connection.
WebSocket API
Viewer.connect(url)-
Set up a web socket connection to a server at the given URL. The viewer will listen for messages on the socket as binary MsgPack blobs. Each message will be decoded using
msgpack.decode()from msgpack-javascript and the resulting object will be passed directly toViewer.handle_command()as documented above.Note that we do support the MsgPack extension types listed in msgpack-javascript#extension-types, with additional support for the
Float32Arraytype which is particularly useful for efficiently sending point data and forUint32Array,Uint8Array, andInt32Array.
Setting opacity
Objects can have their opacity changed in the obvious way, i.e.:
{
type: "set_property",
path: "/path/to/my/geometry",
property: "opacity",
value: 0.5
}
This would assign the opacity value 0.5 to all of the materials found rooted
at "/path/to/my/geometry". (That means using the path "/path" could affect
many geometries.) However, this will overwrite whatever opacity the geometry had
inherently (i.e., from the material defined in the mesh file); a transparent
object could become more opaque.
Meshcat offers a pseudo property "modulated_opacity". Meshcat remembers an object's inherent opacity and, by setting this value, sets the rendered opacity to be the product of the inherent and modulated opacity value. The corresponding command would be:
{
type: "set_property",
path: "/path/to/my/geometry",
property: "modulated_opacity",
value: 0.5
}
Setting "modulated_opacity" to 1 will restore the geometry's original
opacity. This does not introduce a queryable modulated_opacity property on
any material. This is what makes it a "pseudo" property. The same tree-based
scope of effect applies to "modulated_opacity" as with "opacity" (and also
the "color" property).
Meshcat always remembers the inherent opacity value. So, if you've overwritten
the value (via setting "opacity" or "color"), you can restore it by setting
the "modulated_opacity" value to 1.0.
Useful Paths
The default MeshCat scene comes with a few objects at pre-set paths. You can replace, delete, or transform these objects just like anything else in the scene.
/Lights/DirectionalLight- The single directional light in the scene.
/Lights/AmbientLight- The ambient light in the scene.
/Grid- The square grid in the x-y plane, with 0.5-unit spacing.
/Axes- The red, green, and blue XYZ triad at the origin of the scene (invisible by default, click on "open controls" in the upper right to toggle its visibility).
/Cameras- The camera from which the scene is rendered (see below for details)
/Background- The background texture, with properties for "top_color" and "bottom_color" as well as a boolean "visible".
/Render Settings/