© 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:

  1. Load objects in a condynsate Project.

  2. 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.