© Copyright, 2025 G. Schaer.
SPDX-License-Identifier: GPL-3.0-only
Project Example 1: The Cart
Import necessary condynsate modules. Also import numpy for array management.
[1]:
import time
from condynsate import Project
from condynsate import __assets__ as assets
import numpy as np
Create a instance of condynsate.Project that does not use the keyboard but does use the animator and the visualizer.
[2]:
proj = Project(keyboard = False,
visualizer = True,
animator = True)
You can open the visualizer by visiting the following URL:
http://127.0.0.1:7000/static/
Load four medium planes into the simulator to represent the ground and walls. Set fixed = True so that they are fixed in space and adjust their initial states so they are oriented properly.
[3]:
# Load a plane with a carpet texture for the ground
ground = proj.load_urdf(assets['plane_medium.urdf'], fixed=True)
ground.links['plane'].set_texture(assets['carpet.png'])
[4]:
# Load and orient a plane with a windowed wall texture for the left wall
left_wall = proj.load_urdf(assets['half_plane_medium.urdf'], fixed=True)
left_wall.links['plane'].set_texture(assets['window_wall.png'])
left_wall.set_initial_state(roll=1.5708, yaw=1.5708, position=(-5,0,2.5)) # This returns 0 on success
[4]:
0
[5]:
# Load and orient a plane with a doored wall texture for the right wall
right_wall = proj.load_urdf(assets['half_plane_medium.urdf'], fixed=True)
right_wall.links['plane'].set_texture(assets['door_wall.png'])
right_wall.set_initial_state(roll=1.5708, yaw=-1.5708, position=(5,0,2.5)) # This returns 0 on success
[5]:
0
[6]:
# Load and orient a plane with a classroom wall texture for the back wall
back_wall = proj.load_urdf(assets['half_plane_medium.urdf'], fixed=True)
back_wall.links['plane'].set_texture(assets['classroom_wall.png'])
back_wall.set_initial_state(roll=1.5708, position=(0,5,2.5)) # This returns 0 on success
[6]:
0
Load the cart into the simulator. Additionally, set the initial position to [0.0, 0.0, 0.126] so that its wheels are just above the ground plane. Finally, set the initial angle of the pendulum to a non zero value
[7]:
# Load and orient a cart carrying an inverted pendulum. Set the initial
# angle of the pendulum to a non-zero angle.
cart = proj.load_urdf(assets['cart.urdf'])
cart.set_initial_state(position=(0,0,0.126)) # This returns 0 on success
cart.joints['chassis_to_arm'].set_initial_state(angle=0.25) # This returns 0 on success
[7]:
0
Note that the cart’s position and pendulum angle updates are not yet reflected in the visualizer. To update this manually, we can call the proj.refresh_visualizer function. Note, however, this is also done automatically every time proj.load_urdf, proj.reset, or proj.step is called.
[8]:
proj.refresh_visualizer() # This returns 0 on success
[8]:
0
Now we create some extra local vars for the logistics of this specific project.
[9]:
# Store the name of each wheel joint for easy iteration
wheel_joint_names = ('chassis_to_wheel_1', 'chassis_to_wheel_2',
'chassis_to_wheel_3', 'chassis_to_wheel_4',)
# Set control constants to keep the pendulum upright
k = np.array([[ 0.664, -0.024, 4.0, -0.0064]])
m_e = np.zeros(4)
n_e = np.zeros(1)
[10]:
# Turn off the axes and grid visualization. Turn on the spotlight
proj.visualizer.set_axes(False) # This returns 0 on success
proj.visualizer.set_grid(False) # This returns 0 on success
proj.visualizer.set_spotlight(on=True) # This returns 0 on success
# Focus the camera on the cart
proj.visualizer.set_cam_target(cart.center_of_mass) # This returns 0 on success
[10]:
0
We will add two lines plots to the animator. Each line plot will have exactly one line one it. These will not appear yet, only after starting the animator GUI will they appear. There are two ways to start the GUI:
proj.resetproj.animator.start
Either is valid. In this case, we will just use the reset function.
[11]:
# Add a line plot to the animator to track the pendulum angle
# The return value of add_lineplot is either a list of or single
# hex value that is used to reference each line on the lineplot
# In this case, n_lines=1, so the return value is just a hex code.
plot1 = proj.animator.add_lineplot(n_lines=1,
color='b',
line_width=2.5,
y_lim=(-30., 30.),
title='Pendulum',
x_label='Time [seconds]',
y_label='Angle [degrees]',
h_zero_line=True,)
[12]:
# Add another line plot to the animator to track the cart x position
plot2 = proj.animator.add_lineplot(n_lines=1,
color='r',
line_width=2.5,
y_lim=(-5., 5.),
title='Cart',
x_label='Time [seconds]',
y_label='Position [meters]',
h_zero_line=True,)
Before running the simulation, we reset the project. This ensure that everything is started, updated, and in the desired initial state. proj.reset should be called before a simulation every time you run one.
[13]:
# Reset the project to its initial state. This is required to
# reset the simulation, reset the visualizer, and reset/start the
# animator.
proj.reset() # This returns 0 on success
[13]:
0
Important Note: After reset is called, the animator GUI will open. It will be unresponsive until it is refreshed by proj. This refreshes occur automatically at every proj.reset, proj.step, proj.await_anykeys, or proj.await_keypress call. Therefore, while the simulation loop is running, or while waiting for user keyboard input, the GUI will be responsive. You can also manually refresh it using the proj.refresh_animator function.
Next we create and run a simulation loop. In this loop, on every step we
Collect state information about the cart and the pendulum angle
Note if the pendulum has fallen down or not
Calculate a torque to apply to the wheels that will keep the pendulum upright
Apply this calculate torque
Add the current pendulum angle and cart x position to the associated line plots
Take a simulation step in real time
[14]:
# Run a 10 second simulation loop
start = time.time()
while proj.simtime <= 10.:
# Read the states of the pendulum and each wheel
pen_state = cart.joints['chassis_to_arm'].state
wheel_states=tuple(cart.joints[n].state for n in wheel_joint_names)
# If the pendulum angle exceeds 90 degrees, a failure condition is
# met. Terminate the simulation loop.
if abs(pen_state.angle) > 1.5708:
print('The pendulum fell.')
break
# Do controls calculations to determine what torque, when applied
# to each wheel, will keep the pendulum upright.
m = np.array([pen_state.omega,
np.mean([s.omega for s in wheel_states]),
pen_state.angle,
np.mean([s.angle for s in wheel_states])])
torque = float(np.clip((-k@(m - m_e) + n_e)[0], -0.75, 0.75))
# Apply the torque we calculated to each wheel
for joint_name in wheel_joint_names:
# This will offset a drawn torque arrow out of the center of
# the wheels so we can see them. It is required to be
# different between the front wheels (1 and 2) and the rear
# wheels (3 and 4) because they are oriented 180 degrees apart
offset = ('3' in joint_name or '4' in joint_name)*0.1-0.05
cart.joints[joint_name].apply_torque(torque,
draw_arrow=True,
arrow_scale=0.33,
arrow_offset=offset)
# Plot the pendulum angle against the current simulation time
angle_deg = pen_state.angle*180./np.pi
proj.animator.lineplot_append_point(plot1, proj.simtime, angle_deg) # This returns 0 on success
# Plot the cart's x position against the current simulation time
cart_xpos = cart.state.position[0]
proj.animator.lineplot_append_point(plot2, proj.simtime, cart_xpos) # This returns 0 on success
# Take a simulation step that attempts real time simulation
proj.step(real_time=True, stable_step=False) # This returns 0 on success
# Note how long the simulation took
print(f"Simuation took {time.time()-start:.2f} seconds.")
Simuation took 10.04 seconds.
Finally, we terminate the project. This is required to save any videos that are recorded and gracefully exit all the children threads (including the animator window). proj.terminate() should be called when done with any member of the condynsate.Project class.
[15]:
proj.terminate() # This returns 0 on success
[15]:
0