© 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:
Initializing the
AnimatorAdding line plots to the
AnimatorUpdating the
Animatorplots 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:
condynsate.Animator.barchart_set_valuecondynsate.Animator.lineplot_append_pointcondynsate.Animator.lineplot_set_datacondynsate.Animator.resetcondynsate.Project.stepcondynsate.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
Get the pendulum’s joint angle
Get the pendulum’s mass’s x-position
Apply a torque to the pendulum joint proportional to the joint angle
Apply a force to the mass proportional to the mass’s x-position
Plot the joint angle and inputs
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.
[ ]: