© Copyright, 2025 G. Schaer.
SPDX-License-Identifier: GPL-3.0-only
Tutorial 0: Introduction
Tutorial Description
This tutorial covers creating a new Project using condynsate. In this tutorial, we will cover how to:
Load objects in a
condynsateProject.Test that objects behave as expected.
We will accomplish these goals by loading a cube 1 meter above a solid ground plane, starting the physics engine, then observing the dynamics of the cube. Note that this project will not coverhow to create your own .urdf files. Instead, we recommend reviewing https://wiki.ros.org/urdf.
Imports
To begin, we import the required dependencies. For most applications, the condynsate.Project class, is used. The Project class automatically combines and managers the condynsate simulation engine, visualization engine, animation engine, and keyboard listener.
We also import condynsate.__assets__. __assets__ is a dictionary that defines the path to all default assets that come with condynsate including .urdf files, .stl files, and .png texture files. In thic case, we use __assets__ to load the default cube and the default ground plane.
[1]:
from condynsate import Project
from condynsate import __assets__ as assets
To see what default assets are available to us, we can list the keys of assets:
[2]:
assets.keys()
[2]:
dict_keys(['arrow_ccw.stl', 'arrow_lin.stl', 'bricks.png', 'carpet.png', 'cart.urdf', 'classroom_wall.png', 'concrete.png', 'cone_1x1_center_origin.stl', 'cone_1x1_lower_origin.stl', 'cube.urdf', 'cube_1x1x1_center_origin.stl', 'cube_1x1x1_lower_origin.stl', 'cylinder_1x1_center_origin.stl', 'cylinder_1x1_lower_origin.stl', 'door_wall.png', 'gyro.urdf', 'gyrocore.stl', 'gyroouter_1-odx0.9id_lower_origin.stl', 'gyroring_0.6-odx0.3-id_center_origin.stl', 'gyroring_0.9-odx0.6-id_center_origin.stl', 'half_plane_medium.urdf', 'missing_tex.png', 'plane.obj', 'plane_big.urdf', 'plane_medium.urdf', 'plane_small.urdf', 'pyramid_1x1x1_center_origin.stl', 'pyramid_1x1x1_lower_origin.stl', 'sphere_1_center_origin.stl', 'sphere_1_lower_origin.stl', 'white_wall.png', 'window_wall.png'])
Initializing the Project Class
To begin, we initialize a member of the Project class. Doing so will automatically initialize a simulation engine and open the visualization engine.
-----------------------------------------------------------------------------
| condynsate.Project |
-----------------------------------------------------------------------------
The Project class ties together a Simulator, Visualizer, Animator, and
Keyboard into class.
Parameters
----------
**kwargs
Keyword Args
------------
simulator_gravity : 3 tuple of floats, optional
The gravity vector used in the simulation. The default value is
(0.0, 0.0, -9.81).
simulator_dt : float, optional
The finite time step size used by the simulator. If set too
small, can result in visualizer, simulator desynch. Too small
is determined by the number of total links in the simulation.
The default value is 0.01.
visualizer : bool, optional
A boolean flag that indicates if the project should include a
visualizer. This visualizer provides a 3D rendering of the
simulation state. The default is True.
visualizer_frame_rate : bool, optional
The frame rate of the visualizer. When None, attempts to run at
unlimited. This is not reccomended because it can cause
communication bottlenecks that cause slow downs. The default
value is 45.
visualizer_record : bool, optional
A boolean flag that indicates if the visualizer will record.
True, all frames from the start function call to the terminate
function call are recorded. After the terminate function call,
these frames are saved with h.264 and outputs in an MP4
container. The saved file name has the form visualizer.mp4.
The default is False.
animator : bool, optional
A boolean flag that indicates if the project should include an
animator. This animator provides real-time 2D plotting.
The default is False.
animator_frame_rate : float, optional
The upper limit of the allowed frame rate in frames per second.
When set, the animator will not update faster than this speed.
The default is 15.0
animator_record : bool, optional
A boolean flag that indicates if the animator should be
recorded. If True, all frames from the start function call to
the terminate function call are recorded. After the terminate
function call, these frames are saved with h.264 and outputs in
an MP4 container. The saved file name has the form
animator.mp4. The default is False.
Attributes
----------
simulator : condynsate.Simulator
The instance of the condynsate.Simulator class used by this project.
visualizer : condynsate.Visualizer or None
The instance of the condynsate.Visualizer class used by this project.
None if this project uses no visualizer.
animator : condynsate.Animator or None
The instance of the condynsate.Animator class used by this project.
None if this project uses no animator.
keyboard : condynsate.Keyboard or None
The instance of the condynsate.Keyboard class used by this project.
None if this project uses no keyboard.
bodies : List of condynsate.simulator.objects.Body
All bodies loaded into the project via the load_urdf fnc.
simtime : float
The current simulation time in seconds.
[3]:
# Create the project
proj = Project()
You can open the visualizer by visiting the following URL:
http://127.0.0.1:7027/static/
You should now see the default empty environment opened in your default web browser.
We are now able to interact with the Visualizer. For now, let’s turn off the grid plane and axes. These are both visual references and not physical objects.
[4]:
proj.visualizer.set_axes(False) # Returns 0 on success
proj.visualizer.set_grid(False) # Returns 0 on success
[4]:
0
Next, we will load our first physics object, the ground plane. To load physics objects we use the condynsate.Project.load_urdf function:
-----------------------------------------------------------------------------
| condynsate.Project.load_urdf |
-----------------------------------------------------------------------------
Loads a body defined by a .URDF file (https://wiki.ros.org/urdf) into
the project.
Parameters
----------
path : string
The path pointing to the .URDF file that defines the body.
**kwargs
Keyword Args
------------
fixed : boolean, optional
A flag that indicates if the body is fixed (has 0 DoF) or free
(has 6 DoF).
Returns
-------
body : condynsate.core.objects.Body
The body added to the simulation. This retured object facilitates
user interaction with the body and its joints and links.
[5]:
# Load a fixed plane as the ground object
ground = proj.load_urdf(assets['plane_medium.urdf'], fixed=True)
Once the ground plane is loaded, we will add a cube and set its initial state to 1 meter above the ground. To se the initial state, we use the condynsate.simulator.objects.Body.set_initial_state function.
-----------------------------------------------------------------------------
| condynsate.simulator.objects.Body.set_initial_state |
-----------------------------------------------------------------------------
Sets the initial state of the body. When the simulation is
reset, this object will be reset to this state.
Parameters
----------
**kwargs
Keyword Args
------------
position : 3 tuple of floats, optional
The XYZ position in world coordinates. The default is (0., 0., 0.)
yaw : float, optional
The (z-y'-x' Tait–Bryan) yaw angle of the object in radians.
pitch : float, optional
The (z-y'-x' Tait–Bryan) pitch angle of the object in radians.
roll : float, optional
The (z-y'-x' Tait–Bryan) roll angle of the object in radians.
velocity : 3 tuple of floats, optional
The XYZ velocity in either world or body coordinates. Body
coordinates are defined based on object's orientation.
The default is (0., 0., 0.)
omega : 3 tuple of floats, optional
The XYZ angular velocity in either world or body coordinates.
Body coordinates are defined based on object's orientation.
The default is (0., 0., 0.)
body : bool, optional
Whether velocity and omega are being set in world or body
coordinates. The default is False
Returns
-------
ret_code : int
0 if successful, -1 if something went wrong.
[6]:
# Load a cube object and set its initial position to 1 meter above the ground
cube = proj.load_urdf(assets['cube.urdf'])
# The origin of the cube is at its center so
# Setting its initial state to (0,0,1.5) places the bottom face 1 meter above the ground
cube.set_initial_state(position=(0,0,1.5)) # Returns 0 on success
[6]:
0
Notice that upon setting the initial state, the Visualizer is not updated automatically. Where as calls to Project member functions will automatically update the Visualizer, calls to other classes’ member functions will not. To reflect our changes, we can now call the condynsate.Project.refresh_visualizer function.
-----------------------------------------------------------------------------
| condynsate.Project.refresh_visualizer |
-----------------------------------------------------------------------------
Refreshes the visualizer to synchronize it to the current simulator
state. This is automatically called by load_urdf, reset, step,
await_keypress, and await_anykeys. Therefore, its use cases are limited
to when bodies are modified outside of the main simulation loop.
Returns
-------
ret_code : int
0 if successful, -1 if something went wrong.
[7]:
proj.refresh_visualizer() # Returns 0 on success
[7]:
0
Running a Simulation Loop
Before running a simulation, it is important to call the condynsate.Project.reset function. This function resets the Simulator, Visualizer, and, if applicable, Animator to the initial state and synchronizes them.
-----------------------------------------------------------------------------
| condynsate.Project.reset |
-----------------------------------------------------------------------------
Resets the simulator, visualizer (if there is one), and animator
(if there is one). If the animator is not already running, starts the
animator then resets it
Returns
-------
ret_code : int
0 if successful, -1 if something went wrong.
[8]:
proj.reset() # Returns 0 on success
[8]:
0
Now everything is set up and we are ready to run a simulation loop. To do so, we use the condynsate.Project.step function. This function takes a single physics step and updates the Visualizer to reflect changes in the Simulator state. We will call the step function in a loop until the simulation time is 2.0 seconds. Doing so will run a 2.0 second simulation.
-----------------------------------------------------------------------------
| condynsate.Project.step |
-----------------------------------------------------------------------------
Takes a single simulation step and updates the visualizer to reflect
the new simulator state.
Parameters
----------
real_time : bool, optional
A boolean flag that indicates whether the step is to be taken in
real time (True) or as fast as possible (False).
The default is True.
stable_step : bool, optional
Boolean flag that indicates the type of real time stepping that
should be used. The default is False.
When real_time is False, this flag is ignored.
When real_time is True and stable_step is False, the time of the
first step() call since instantiation or reset is noted. Then, at
every subsequent step() call, the function will sleep until it has
been exactly dt*(n-1) seconds since the noted epoch. dt is the
simulator time step and n is the number of times step() has been
called since instantiation or reset. This guarantees a more
accurate total run time, but less stable frame rates, especially
if, at any point while running a simulation, there is a long pause
between step() calls.
When real_time and stable_step are True, the function will sleep
until the duration since the last time step() was called is equal
to the time step of the simulation. This guarantees a more stable
frame rate, but less accurate total run time.
Returns
-------
ret_code : int
0 if successful, -1 if something went wrong.
[9]:
while proj.simtime <= 2.:
proj.step(real_time=True, # Run the simulation in real time
stable_step=False # Dynamically adjust the refresh rate for best total run time
)
When we are done using a Project object, it is imperative we call the condynsate.Project.terminate function. Doing so will save all recordings, if any were captured, and ensure all children threads exit gracefully.
-----------------------------------------------------------------------------
| condynsate.Project.terminate |
-----------------------------------------------------------------------------
Gracefully terminates the project and all children threads.
Returns
-------
ret_code : int
0 if successful, -1 if something went wrong.
[10]:
proj.terminate() # Returns 0 on success
[10]:
0
Challenge
This tutorial is now complete. For an added challenge, think of how you would modify this project so that two cubes, one above the other, are loaded but only the top one has 6 degrees of freedom.