© Copyright, 2025 G. Schaer.

SPDX-License-Identifier: GPL-3.0-only

Tutorial 4: The Animator

Tutorial Description

This tutorial covers creating a condynsate Project with an Animator. We will cover:

  1. Initializing the Animator

  2. Adding line plots to the Animator

  3. Updating the Animator plots during simulation

Imports

To begin, we import the same modules for the same reasons as tutorial 0.

[1]:
from condynsate import Project
from condynsate import __assets__ as assets

Initializing the Project Class

Here we initialize the Project in the same way as Tutorial 2, however, we set the Animator flag to True. This tells the Project to initialize with an internal Animator instance.

[2]:
# Create the project
proj = Project(animator=True)
You can open the visualizer by visiting the following URL:
http://127.0.0.1:7031/static/
[3]:
proj.visualizer.set_axes(False) # Returns 0 on success
[3]:
0
[4]:
# Load a pendulum object and set its initial position to resting on the ground
pendulum = proj.load_urdf(assets['pendulum.urdf'],
                          fixed=True
                         )
[5]:
# Set the initial angle of the pendulum arm
pendulum.joints['base_to_arm'].set_initial_state(angle=0.1745) # All angles in condynsate are radians
[5]:
0
[6]:
# Refresh the visualizer to show changes to the pendulums's position and angle
proj.refresh_visualizer() # Returns 0 on success
[6]:
0

Adding Plots to the Animator

We will now add two plots to the Animator. The first plot will be used to track the angle of the pendulum and the second plot will be used to track the inputs applied. To add line plots to the Animator, we use the condynsate.Animator.add_lineplot function.

-----------------------------------------------------------------------------
| condynsate.Animator.add_lineplot                                          |
-----------------------------------------------------------------------------
Adds a lineplot to the animator window. Neither the lineplot nor the
window appear until the user calls the start function.

Parameters
----------
n_lines : int
    The number of lines that are drawn on the plot. Must be integer
    between [1, 16].
**kwargs

Keyword Args
------------
x_lim : [float, float], optional
    The limits to apply to the x axis of the plot. A value of None
    will apply automatically updating limits to the corresponding
    bound of the axis. For example [None, 10.] will fix the upper
    bound to exactly 10, but the lower bound will freely change to
    show all data.The default is [None, None].
y_lim : [float, float], optional
    The limits to apply to the y axis of the plot. A value of None
    will apply automatically updating limits to the corresponding
    bound of the axis. For example [None, 10.] will fix the upper
    bound to exactly 10, but the lower bound will freely change to
    show all data.The default is [None, None].
h_zero_line : boolean, optional
    A boolean flag that indicates whether a horizontal line will be
    drawn on the y=0 line. The default is false
v_zero_line : boolean, optional
    A boolean flag that indicates whether a vertical line will be
    drawn on the x=0 line. The default is false
tail : int or tuple of ints optional
    Specifies how many data points are used to draw a line. Only
    the most recently added data points are kept. Any data points
    added more than tail data points ago are discarded and not
    plotted. When tuple, must have length n_lines. A value less
    than or equal to 0 means that no data is ever discarded and all
    data points added to the animator will be drawn. The default
    is -1.
title : string, optional
    The title of the plot. Will be written above the plot when
    rendered. The default is None.
x_label : string, optional
    The label to apply to the x axis. Will be written under the
    plot when rendered. The default is None.
y_label : string, optional
    The label to apply to the y axis. Will be written to the
    left of the plot when rendered. The default is None.
label : string or tuple of strings, optional
    The label applied to each artist. The labels are shown in a
    legend in the top right of the plot. When tuple, must have
    length n_lines. When None, no labels are made. The default
    is None.
color : matplotlib color string or tuple of color strings, optional
    The color each artist draws in. When tuple, must have length
    n_lines. The default is 'black'.
line_width : float or tuple of floats, optional
    The line weigth each artist uses. When tuple, must have length
    n_lines. The default is 1.5.
line_style : line style string or tuple of ls strings, optional
    The line style each artist uses. When tuple, must have length
    n_lines. The default is 'solid'. Select from 'solid', 'dashed',
    'dashdot', or 'dotted'.

Raises
------
RuntimeError
    If cannot add another subplot or the animator was already running.
    Can only add up to 16 subplots total.

ValueError
    If n_lines is not an int or is less than 1 or greater than 16.

Returns
-------
lines_ids : hex or tuple of hex
    A unique identifier that allows the user to address each line
    in the lineplot. For example, if n_lines = 3, the tuple will have
    length three; however, if n_lines = 1, the returned value will be
    the hex id of the only line (not a tuple).
[7]:
# Add a line plot to the animator to track the pendulum angle
plot1 = proj.animator.add_lineplot(n_lines=1, # This argument tells us there is only 1 line on the plot
                                   y_lim=(-30., 30.), # We can set the lower and upper y limits of the plot
                                   title='Pendulum',
                                   x_label='Time [seconds]',
                                   y_label='Angle [degrees]',
                                   h_zero_line=True, # This tells the animator to draw a horizontal line at y=0
                                   color='k', # Set the color of the line
                                   line_width=2.5 # Set the width of the line
                                  )

[8]:
# Add another line plot to the animator to track the applied inputs
plot2 = proj.animator.add_lineplot(n_lines=2, # This argument tells us there are 2 lines on the plot
                                   title='External Inputs',
                                   x_label='Time [seconds]',
                                   y_label='Inputs',
                                   h_zero_line=True,
                                   color=('r', 'b'),  # We give each line a different color
                                   label=('Torque [N-m]', 'Force [N]'), # This argument tells the legend what each line is
                                   line_width=2.5 # Set the widths of both lines to 2.5
                                  )

Observing the returned values:

[9]:
plot1
[9]:
'0x0'
[10]:
plot2
[10]:
('0x10', '0x11')

plot1 is a single hex value used to index the only line on the first line plot while plot2 is a tuple of 2 hex values. This allows us to index each line in plot 2 separately.

Running a Simulation Loop

Similary to Tutorial 0, we start the simulation loop by calling condynsate.Project.reset. Note that when the Project is initialized with an Animator, the reset function will automatically open the Animator GUI.

Important Note: The GUI does not refresh regularly. This means it is up to the user to keep the window responsive. The GUI will refresh, and therefore become responsive, when any of the following functions are called:

  1. condynsate.Animator.barchart_set_value

  2. condynsate.Animator.lineplot_append_point

  3. condynsate.Animator.lineplot_set_data

  4. condynsate.Animator.reset

  5. condynsate.Project.step

  6. condynsate.Project.refresh_animator

It is necessary, then, to call at least one of these functions regularly. For a typical project, we maintain Animator responsiveness through regular calls of the condynsate.Project.step function in the simulation loop.

[11]:
proj.reset() # Returns 0 on success
[11]:
0

In each step of the loop we take 4 steps

  1. Get the pendulum’s joint angle

  2. Get the pendulum’s mass’s x-position

  3. Apply a torque to the pendulum joint proportional to the joint angle

  4. Apply a force to the mass proportional to the mass’s x-position

  5. Plot the joint angle and inputs

  6. Take a single simulation step

To append data points to the end of a line in a line plot, we use the condynsate.Animator.lineplot_append_point function

-----------------------------------------------------------------------------
| condynsate.Animator.lineplot_append_point                                 |
-----------------------------------------------------------------------------
Appends a single y versus x data point to the end of a line.

Parameters
----------
line_id : hex string
    The id of the line to which a point is appended.
x_val : float
    The x coordinate of the data point being appended.
y_val : float
    The y coordinate of the data point being appended.

Returns
-------
ret_code : int
    0 if successful, -1 if something went wrong.
[12]:
# Run a 10 second simulation
while proj.simtime <= 10.:

    # Get the pendulum's joint angle
    joint_state = pendulum.joints['base_to_arm'].state
    angle = joint_state.angle

    # Get the pendulum's mass's x position
    link_state = pendulum.links['mass'].state
    x_position = link_state.position[0]

    # Apply a torque to the pendulum's joint
    torque = -7.0 * angle
    pendulum.joints['base_to_arm'].apply_torque(torque, draw_arrow=True, arrow_scale=0.2)

    # Apply a force to the pendulum's mass
    force = (-10.0 * x_position, 0, 0)
    pendulum.links['mass'].apply_force(force, draw_arrow=True, arrow_scale=0.2)

    # Append the data points to the plots
    proj.animator.lineplot_append_point(plot1, proj.simtime, angle*180./3.1415926)
    proj.animator.lineplot_append_point(plot2[0], proj.simtime, torque)
    proj.animator.lineplot_append_point(plot2[1], proj.simtime, force[0])

    # Take a single simulation step
    proj.step(real_time=True, # Run the simulation in real time
              stable_step=False # Dynamically adjust the refresh rate for best total run time
             )

Finally, we ensure all children threads exit gracefully. Terminating will also close the Animator GUI.

[13]:
proj.terminate() # Returns 0 on success
[13]:
0

Challenge

This tutorial is now complete. For an additional challenge, think of how you might adjust the first plot to also show the angular velocity as a function of time.

[ ]: