Visualization

moro provides visualization tools for inspecting robot configurations and animations.

The main interface is RobotVisualizer, which takes a Robot model and evaluates its symbolic kinematics at numerical configurations before passing the resulting scene to one of the available rendering backends.

Two backends are currently available:

  • Matplotlib, for static 3D plots and Python-side animations;

  • Three.js, for interactive browser-based visualization.

This section focuses on the recommended high-level workflow. Lower-level visualization objects such as SceneData, FrameData, and the backend classes are documented in the API Reference.

Creating a visualizer

Start by defining a robot:

from moro import Robot
from moro.abc import q1, q2, l1, l2

robot = Robot(
    (l1, 0, 0, q1, "r"),
    (l2, 0, 0, q2, "r"),
)

Then create a visualizer:

from moro.visualization import RobotVisualizer

viz = RobotVisualizer(robot)

A RobotVisualizer is associated with a single Robot instance.

The same visualizer can then be reused for different configurations, backends, and animations.

Plotting a robot configuration

A single robot configuration is displayed with:

viz.plot(...)

For example:

values = {
    l1: 1.0,
    l2: 0.8,
    q1: 0.5,
    q2: 0.8,
}

viz.plot(values)

The first argument is a dictionary that maps symbolic robot parameters to numerical values.

Because Matplotlib is the default backend, the previous call is equivalent to:

viz.plot(
    values,
    backend="matplotlib",
)

Providing numerical values

Visualization requires numerical frame transformations.

For a fully numerical robot model, only the joint variables may need to be supplied:

robot = Robot(
    (1.0, 0, 0, q1, "r"),
    (0.8, 0, 0, q2, "r"),
)

values = {
    q1: 0.5,
    q2: 0.8,
}

For a symbolic robot model:

robot = Robot(
    (l1, 0, 0, q1, "r"),
    (l2, 0, 0, q2, "r"),
)

the geometric parameters must also be provided:

values = {
    l1: 1.0,
    l2: 0.8,
    q1: 0.5,
    q2: 0.8,
}

Internally, moro evaluates the transformation of each robot frame with respect to the base frame and converts the resulting matrices to numerical scene data.

Therefore, all symbolic quantities required by the robot transformations must have numerical values before rendering.

Choosing a backend

The backend is selected with:

backend=...

The available values are:

"matplotlib"
"threejs"

For example:

viz.plot(
    values,
    backend="matplotlib",
)

or:

viz.plot(
    values,
    backend="threejs",
)

Both backends use the same evaluated robot geometry and the same VisualizationStyle interface, but their rendering capabilities differ.

Matplotlib visualization

The Matplotlib backend creates a standard 3D Matplotlib figure.

Use:

fig, ax = viz.plot(
    values,
    backend="matplotlib",
)

The result contains:

fig → Matplotlib Figure
ax  → 3D Matplotlib Axes

This makes it possible to continue customizing the plot using normal Matplotlib operations.

For example:

ax.set_title("Planar 2R robot")

The backend does not call plt.show() automatically, so display remains under user control.

In scripts, the figure can be shown with:

import matplotlib.pyplot as plt

plt.show()

Figure size

The default figure size is:

figsize=(10, 8)

and can be changed with:

fig, ax = viz.plot(
    values,
    backend="matplotlib",
    figsize=(8, 6),
)

Camera orientation

The initial camera orientation can be configured with:

view_init=(elevation, azimuth)

For example:

fig, ax = viz.plot(
    values,
    backend="matplotlib",
    view_init=(40, 25),
)

This follows the usual Matplotlib 3D view convention.

Interactive Three.js visualization

The Three.js backend produces an interactive view intended primarily for notebook environments.

Use:

viz.plot(
    values,
    backend="threejs",
)

The result is returned as an IPython.display.HTML object and can be displayed directly in environments such as Jupyter Notebook or JupyterLab.

The default rendering area is:

width=800
height=600

and can be changed with:

viz.plot(
    values,
    backend="threejs",
    width=900,
    height=650,
)

Preset views

The interactive viewer includes buttons for:

Front
Top
Isometric

These controls change the camera directly inside the viewer.

The initial view is isometric.

Camera type

The viewer supports both:

Orthographic
Perspective

projection.

The orthographic camera is selected initially.

The camera type can be changed interactively without recomputing the robot model.

Interactive navigation

The Three.js viewer also provides free camera navigation through orbit controls.

The user can rotate, zoom, and inspect the robot from arbitrary directions.

A reset control restores the default view.

The preset view and camera controls belong to the Three.js interface itself; they are not arguments of RobotVisualizer.plot().

Customizing the visualization

Visual appearance is configured through:

from moro.visualization import VisualizationStyle

For example:

style = VisualizationStyle(
    show_frames=False,
    show_grid=True,
    link_linewidth=5,
)

Then pass the same style object to either backend:

viz.plot(
    values,
    backend="matplotlib",
    style=style,
)

or:

viz.plot(
    values,
    backend="threejs",
    style=style,
)

Visibility options

The main visibility controls are:

VisualizationStyle(
    show_frames=True,
    show_links=True,
    show_joints=True,
    show_base=True,
    show_grid=True,
)

For example, to display only the robot links and joints:

style = VisualizationStyle(
    show_frames=False,
    show_grid=False,
)

Colors

The default colors can also be changed:

style = VisualizationStyle(
    link_color="#444444",
    joint_color="#d62728",
    base_color="#000000",
)

Object sizes

The following properties control the size of the displayed elements:

frame_scale
joint_size
base_size
link_linewidth

When:

frame_scale=None
joint_size=None
base_size=None

their values are derived automatically from the dimensions of the robot scene.

This makes the default visualization scale appropriately for robots of different sizes.

Animating robot configurations

A sequence of robot configurations can be animated with:

viz.animate(...)

The input is a sequence of substitution dictionaries.

For example:

configurations = [
    {
        l1: 1.0,
        l2: 0.8,
        q1: 0.0,
        q2: 0.0,
    },
    {
        l1: 1.0,
        l2: 0.8,
        q1: 0.2,
        q2: 0.1,
    },
    {
        l1: 1.0,
        l2: 0.8,
        q1: 0.4,
        q2: 0.2,
    },
]

Then:

animation = viz.animate(
    configurations,
)

Again, Matplotlib is the default backend.

An empty configuration sequence is not valid.

Matplotlib animations

Use:

animation = viz.animate(
    configurations,
    backend="matplotlib",
)

The result is a:

matplotlib.animation.FuncAnimation

Keep a reference to the returned animation object until it has been displayed or saved.

For example:

animation = viz.animate(
    configurations,
    backend="matplotlib",
)

rather than calling viz.animate(...) without assigning the result.

Frame interval

The default interval is:

interval=100

milliseconds.

It can be changed with:

animation = viz.animate(
    configurations,
    backend="matplotlib",
    interval=50,
)

The spatial limits of the animation are computed from the complete sequence, so the camera scale remains fixed while the robot moves.

Three.js animations

Use:

viz.animate(
    configurations,
    backend="threejs",
)

The result is an interactive HTML animation.

The viewer includes controls for:

  • Play and Pause;

  • direct frame selection;

  • a frame slider;

  • Front, Top, and Isometric views;

  • orthographic and perspective cameras;

  • camera reset;

  • free orbit navigation.

The animation uses one global spatial scale for the complete sequence, so the apparent sizes of links, joints, and coordinate frames remain consistent throughout the motion.

Showing the end-effector trajectory

Animations can optionally display the Cartesian path followed by the end-effector.

Create a style with:

style = VisualizationStyle(
    show_trajectory=True,
)

and use it in the animation:

viz.animate(
    configurations,
    backend="threejs",
    style=style,
)

The same option is supported by the Matplotlib backend.

The displayed trajectory corresponds to the positions of the final robot frame throughout the animation sequence.

Trajectory display modes

Two display modes are available:

"full"
"trace"

The default is:

trajectory_mode="full"

With "full", the complete end-effector trajectory is displayed.

With:

style = VisualizationStyle(
    show_trajectory=True,
    trajectory_mode="trace",
)

the displayed path grows as the animation advances.

Trajectory appearance can also be customized with:

trajectory_color
trajectory_linewidth

For example:

style = VisualizationStyle(
    show_trajectory=True,
    trajectory_color="#222222",
    trajectory_linewidth=3,
)

Animating an inverse-kinematics trajectory

RobotVisualizer.animate() expects substitution dictionaries, while solve_position_trajectory() returns joint vectors through:

trajectory.qs

The two APIs can be connected easily.

Suppose an IK trajectory has already been computed:

trajectory = solve_position_trajectory(
    robot,
    targets,
    q0=[0.1, 0.1],
    parameters={
        l1: 1.0,
        l2: 0.8,
    },
)

First verify that the trajectory converged:

if trajectory.converged:
    print("Trajectory solved.")

Then convert each joint vector into a substitution dictionary:

configurations = [
    dict(zip(robot.qs, q))
    for q in trajectory.qs
]

For a robot with symbolic geometric parameters, include them in each configuration:

parameters = {
    l1: 1.0,
    l2: 0.8,
}

configurations = [
    {
        **parameters,
        **dict(zip(robot.qs, q)),
    }
    for q in trajectory.qs
]

Now animate the result:

style = VisualizationStyle(
    show_trajectory=True,
    trajectory_mode="trace",
)

viz.animate(
    configurations,
    backend="threejs",
    style=style,
)

This provides a convenient workflow from Cartesian position targets to a visual representation of the corresponding robot motion.

A worked example

Consider a planar 2R robot:

import numpy as np

from moro import Robot
from moro.abc import q1, q2, l1, l2
from moro.visualization import (
    RobotVisualizer,
    VisualizationStyle,
)

robot = Robot(
    (l1, 0, 0, q1, "r"),
    (l2, 0, 0, q2, "r"),
)

viz = RobotVisualizer(robot)

Define the geometric parameters and one configuration:

values = {
    l1: 1.0,
    l2: 0.8,
    q1: np.pi / 4,
    q2: -np.pi / 6,
}

A static Matplotlib plot can be created with:

fig, ax = viz.plot(
    values,
    backend="matplotlib",
    view_init=(35, 35),
)

The same configuration can be inspected interactively with Three.js:

viz.plot(
    values,
    backend="threejs",
)

Now create a sequence of configurations:

configurations = []

for theta in np.linspace(0, np.pi / 2, 40):
    configurations.append({
        l1: 1.0,
        l2: 0.8,
        q1: theta,
        q2: -theta / 2,
    })

Create a visualization style that shows the end-effector path:

style = VisualizationStyle(
    show_trajectory=True,
    trajectory_mode="trace",
)

Then animate the robot:

viz.animate(
    configurations,
    backend="threejs",
    style=style,
)

The resulting viewer allows the animation to be played, paused, inspected frame by frame, and viewed using different camera projections.

The same data can be rendered with Matplotlib:

animation = viz.animate(
    configurations,
    backend="matplotlib",
    interval=75,
    style=style,
)

Notes and limitations

The visualization module is intended primarily for inspecting kinematic robot models and computed joint trajectories.

Keep the following points in mind:

  • visualization operates on the Robot kinematic model;

  • all symbolic quantities required by the robot transformations must be assigned numerical values;

  • RobotVisualizer.plot() renders one configuration;

  • RobotVisualizer.animate() renders a sequence of configurations;

  • Matplotlib is the default backend;

  • the Three.js backend returns interactive HTML and is especially convenient in notebook environments;

  • Three.js preset views and camera types are controlled from the viewer interface rather than through RobotVisualizer.plot() arguments;

  • visualization styles can be shared between both backends;

  • automatic scene scaling is used when explicit object sizes are not provided;

  • animation scaling remains fixed across the complete sequence;

  • the optional trajectory display corresponds to the Cartesian path of the final robot frame.

The current visualization module represents robots using kinematic links, joints, and coordinate frames. It is not intended to provide detailed CAD geometry or physically realistic rendering.

The visualization system does not currently provide:

  • collision detection;

  • contact visualization;

  • physics simulation;

  • URDF or mesh-based robot geometry;

  • automatic trajectory generation;

  • interactive joint sliders;

  • dynamic-force or torque visualization.

These capabilities may require additional modeling or visualization infrastructure beyond the current scope.

See also

  • Robot Modeling — define the serial robot to be visualized.

  • Forward Kinematics — compute the frame transformations used by the visualization system.

  • Inverse Kinematics — generate joint configurations for Cartesian targets and trajectories.

  • Jacobians — inspect differential kinematic quantities associated with the robot motion.

  • API Reference → Visualization — complete reference for RobotVisualizer, VisualizationStyle, backend classes, and scene-data types.