Skip to content

Commit 9bc5f0c

Browse files
projectairsim runtime (#190)
Co-authored-by: Lucas Scheinkerman <lucas.scheinkerman@gmail.com>
1 parent 3c0df65 commit 9bc5f0c

16 files changed

Lines changed: 733 additions & 91 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,24 @@ add_subdirectory(unity)
873873
add_subdirectory(vehicle_apis)
874874

875875
if(BUILD_TESTING)
876+
# Stage shared test runtime dependencies exactly once. Multiple PRE_LINK and
877+
# POST_BUILD copies to SIMLIBS_TEST_DIR can run concurrently under Ninja and
878+
# intermittently fail on Windows when they touch the same destination files.
879+
add_custom_target(simlibs_test_runtime_dependencies
880+
COMMAND ${CMAKE_COMMAND} -E make_directory "${SIMLIBS_TEST_DIR}"
881+
COMMAND ${CMAKE_COMMAND} -E copy_directory
882+
"${UE_PLUGIN_SIMLIBS_DIR}/shared_libs"
883+
"${SIMLIBS_TEST_DIR}"
884+
COMMAND ${CMAKE_COMMAND} -E copy_directory
885+
"${JSBSIM_CORESIM_DIR}/lib/$<IF:$<CONFIG:Release>,Release,Debug>"
886+
"${SIMLIBS_TEST_DIR}"
887+
COMMENT "Staging SimLibs test runtime dependencies"
888+
)
889+
add_dependencies(simlibs_test_runtime_dependencies jsbsim-repo)
890+
if(UNIX)
891+
add_dependencies(simlibs_test_runtime_dependencies zlib-external)
892+
endif()
893+
876894
set(SIMLIBS_UNIT_TEST_TARGETS
877895
core_sim_gtests
878896
physics_gtests
@@ -888,6 +906,7 @@ if(BUILD_TESTING)
888906
add_custom_target(simlibs_unit_tests)
889907
foreach(TEST_TARGET ${SIMLIBS_UNIT_TEST_TARGETS})
890908
if(TARGET ${TEST_TARGET})
909+
add_dependencies(${TEST_TARGET} simlibs_test_runtime_dependencies)
891910
add_dependencies(simlibs_unit_tests ${TEST_TARGET})
892911
endif()
893912
endforeach()

‎README.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ See **[Installing system prerequisites](docs/system_specs.md#installing-system-p
8484

8585
## Quick Start: Run from Source
8686

87+
For controller, client, API, and physics iterations that do not need Unreal
88+
rendering or sensors, use **[Project AirSim Runtime](samples/projectairsim_runtime/README.md)**. It
89+
is the lightweight Project AirSim host and includes flat-ground collision
90+
response for Fast Physics vehicles and landing gear.
91+
8792
Follow these steps to set up and run Project AirSim from source:
8893

8994
### 1. Install Unreal Engine versions 5.2 or 5.7

‎client/python/example_user_scripts/airtaxi_dynamic_city_traj.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,4 @@ async def main(scenefile):
233233
if __name__ == "__main__":
234234
scene_to_run = "scene_quadtiltrotor_dfw_dynamic_city.jsonc"
235235
projectairsim_log().info('Using scene "' + scene_to_run + '"')
236-
asyncio.run(main(scene_to_run)) # Runner for async main function
236+
asyncio.run(main(scene_to_run)) # Runner for async main function
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""
2+
Copyright (C) 2025 IAMAI CONSULTING CORP
3+
MIT License.
4+
5+
Runs a Project AirSim Runtime flight and displays it in the drone position viewer.
6+
7+
Run from the repository root after starting Project AirSim Runtime. See
8+
samples/projectairsim_runtime/README.md for Windows and Linux build/run instructions.
9+
"""
10+
11+
import asyncio
12+
import threading
13+
from pathlib import Path
14+
15+
from projectairsim import ProjectAirSimClient, Drone, World
16+
from projectairsim.drone_position_viewer import DronePositionViewer
17+
from projectairsim.utils import projectairsim_log
18+
19+
# Resolve the shared sim_config directory even when running from subfolders.
20+
SIM_CONFIG_PATH = str((Path(__file__).resolve().parent.parent / "sim_config").resolve())
21+
22+
23+
async def drone_sequence(drone):
24+
api_control_enabled = False
25+
armed = False
26+
try:
27+
# Set the drone to be ready to fly.
28+
drone.enable_api_control()
29+
api_control_enabled = True
30+
drone.arm()
31+
armed = True
32+
33+
# Command the vehicle to take off and wait until completion.
34+
projectairsim_log().info("takeoff_async: starting")
35+
takeoff_task = await drone.takeoff_async()
36+
await takeoff_task
37+
projectairsim_log().info("takeoff_async: completed")
38+
39+
# Move up at one meter per second in NED coordinates.
40+
move_up_task = await drone.move_by_velocity_async(
41+
v_north=0.0, v_east=0.0, v_down=-1.0, duration=2.0
42+
)
43+
projectairsim_log().info("Move-Up invoked")
44+
45+
await move_up_task
46+
projectairsim_log().info("Move-Up completed")
47+
48+
# Move diagonally while maintaining altitude.
49+
move_diagonal_task = await drone.move_by_velocity_async(
50+
v_north=-1.0, v_east=1.0, v_down=0.0, duration=3.0
51+
)
52+
projectairsim_log().info("Move diagonally invoked")
53+
54+
await move_diagonal_task
55+
projectairsim_log().info("Move diagonally completed")
56+
57+
# Descend and poll task completion to keep this flow explicit.
58+
move_down_task = await drone.move_by_velocity_async(
59+
v_north=0.0, v_east=0.0, v_down=1.0, duration=2.0
60+
)
61+
projectairsim_log().info("Move-Down invoked")
62+
63+
while not move_down_task.done():
64+
await asyncio.sleep(0.005)
65+
projectairsim_log().info("Move-Down completed")
66+
finally:
67+
if armed:
68+
drone.disarm()
69+
if api_control_enabled:
70+
drone.disable_api_control()
71+
72+
projectairsim_log().info("Project AirSim Runtime demo completed")
73+
74+
75+
def start_drone_thread(drone, completed_event, errors):
76+
def run_sequence():
77+
try:
78+
asyncio.run(drone_sequence(drone))
79+
except Exception as err:
80+
errors.append(err)
81+
finally:
82+
completed_event.set()
83+
84+
thread = threading.Thread(
85+
target=run_sequence,
86+
daemon=True,
87+
)
88+
thread.start()
89+
return thread
90+
91+
92+
async def main():
93+
# Create a simulation client.
94+
client = ProjectAirSimClient()
95+
connected = False
96+
drone_thread = None
97+
demo_completed = threading.Event()
98+
drone_errors = []
99+
100+
try:
101+
# Connect to simulation environment.
102+
client.connect()
103+
connected = True
104+
105+
# Create a World object to interact with the sim world and load a scene.
106+
world = World(
107+
client,
108+
"scene_basic_drone.jsonc",
109+
delay_after_load_sec=2,
110+
sim_config_path=SIM_CONFIG_PATH,
111+
)
112+
113+
# Create a Drone object to interact with a drone in the loaded sim world.
114+
drone = Drone(client, world, "Drone1")
115+
drone_thread = start_drone_thread(drone, demo_completed, drone_errors)
116+
# Tk must run on the main thread. The viewer closes when the flight ends.
117+
DronePositionViewer(drone, close_event=demo_completed)
118+
119+
drone_thread.join()
120+
if drone_errors:
121+
raise drone_errors[0]
122+
123+
except Exception as err:
124+
projectairsim_log().error(f"Exception occurred: {err}")
125+
raise
126+
127+
finally:
128+
# Always disconnect to allow clean reconnection in the next run.
129+
if connected:
130+
client.disconnect()
131+
if drone_thread is not None and drone_thread.is_alive():
132+
drone_thread.join()
133+
134+
135+
if __name__ == "__main__":
136+
asyncio.run(main())

‎client/python/projectairsim/src/projectairsim/drone_position_viewer.py‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@
44

55
import tkinter as tk
66
import threading
7-
import time
8-
9-
from projectairsim import ProjectAirSimClient, Drone, World
10-
from projectairsim.utils import projectairsim_log
11-
127

138
# -----------------------------------------------
149
# Drone Position Viewer
@@ -158,7 +153,7 @@ def update_view(self, x, y, altitude):
158153
screen_y + self.dot_size
159154
)
160155

161-
# ---------- ALTITUDE BAR UPDATE (SMOOTH + CLEAN LABELS) ----------
156+
# ---------- ALTITUDE BAR UPDATE (SMOOTH + CLEAN LABELS) ----------
162157
ALT_PIXELS = 100 # pixels per meter
163158
TICK_STEP = 0.05 # fine ticks every 0.05 m (smooth scrolling)
164159
VIEW_RANGE = 5.0 # ±5m window
@@ -226,4 +221,6 @@ def update_view(self, x, y, altitude):
226221

227222
def on_close(self):
228223
self.stop_event.set()
224+
if self.refresh_after_id is not None:
225+
self.root.after_cancel(self.refresh_after_id)
229226
self.root.destroy()

‎core_sim/test/CMakeLists.txt‎

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -147,18 +147,6 @@ target_include_directories(${TARGET_NAME} PRIVATE "${ONNXRUNTIME_ROOTDIR}/includ
147147
target_link_directories(${TARGET_NAME} PRIVATE "${ONNX_LIB_DIR}")
148148
target_link_libraries(${TARGET_NAME} PRIVATE onnxruntime jsbsim)
149149

150-
add_custom_command(
151-
TARGET ${TARGET_NAME}
152-
PRE_LINK
153-
COMMAND ${CMAKE_COMMAND} -E copy_directory ${UE_PLUGIN_SIMLIBS_DIR}/shared_libs/ ${SIMLIBS_TEST_DIR}
154-
)
155-
156-
add_custom_command(
157-
TARGET ${TARGET_NAME}
158-
PRE_LINK
159-
COMMAND ${CMAKE_COMMAND} -E copy_directory ${JSBSIM_CORESIM_DIR}/lib/$<IF:$<CONFIG:Release>,Release,Debug>/ ${SIMLIBS_TEST_DIR}
160-
)
161-
162150
# Include CMake's GoogleTest module to use gtest_discover_tests() helper
163151
include(GoogleTest)
164152
gtest_discover_tests(${TARGET_NAME})

‎docs/changelog.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1818

1919
### Fixed
2020
- Coordinate conversion precision in the Python ROS bridge
21-
- GPU LiDAR behavior and standalone simulator builds on Unreal Engine 5.7
21+
- GPU LiDAR behavior and Project AirSim Runtime builds on Unreal Engine 5.7
2222

2323
## [0.2.0] - 2026-05-29
2424
### Added

‎physics/test/CMakeLists.txt‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ add_custom_command(TARGET ${TARGET_NAME}
100100
POST_BUILD
101101
COMMAND ${CMAKE_COMMAND} -E echo "Copying [${TARGET_NAME}] test data to ${SIMLIBS_TEST_DIR}/test_data"
102102
COMMAND ${CMAKE_COMMAND} -E copy_directory ${TARGET_SOURCE_DIR}/test/test_data ${SIMLIBS_TEST_DIR}/test_data
103-
COMMAND ${CMAKE_COMMAND} -E copy_directory ${JSBSIM_CORESIM_DIR}/lib/$<IF:$<CONFIG:Release>,Release,Debug>/ ${SIMLIBS_TEST_DIR}
104103
)
105104

106105
# Include CMake's GoogleTest module to use gtest_discover_tests() helper

‎samples/CMakeLists.txt‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,4 @@
1515
#
1616
# ---------------------------------------------------------------------------------------------------------------------
1717

18-
# add_subdirectory(standalone_sim)
19-
add_subdirectory(standalone_sim)
18+
add_subdirectory(projectairsim_runtime)

samples/standalone_sim/CMakeLists.txt renamed to samples/projectairsim_runtime/CMakeLists.txt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
#
1616
# ---------------------------------------------------------------------------------------------------------------------
1717

18-
set(TARGET_NAME standalone_sim)
18+
set(TARGET_NAME projectairsim-runtime)
1919

2020
add_executable(
2121
${TARGET_NAME}
22+
ground_collision_host.cpp
2223
main.cpp
2324
)
2425

@@ -94,3 +95,25 @@ else()
9495
dl # Required by static libcrypto (dlopen/dlsym)
9596
)
9697
endif()
98+
99+
# Keep the standalone runtime self-contained. Both core_sim and physics use
100+
# shared third-party libraries, so launching the executable directly from its
101+
# documented output directory must not depend on a developer shell or PATH.
102+
if(WIN32)
103+
set(RUNTIME_ONNX_SHARED_LIB "${ONNX_LIB_DIR}/onnxruntime.dll")
104+
else()
105+
set(RUNTIME_ONNX_SHARED_LIB "${ONNXRUNTIME_LIBRARY}")
106+
set_target_properties(${TARGET_NAME} PROPERTIES BUILD_RPATH "$ORIGIN")
107+
endif()
108+
109+
add_custom_command(
110+
TARGET ${TARGET_NAME}
111+
POST_BUILD
112+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
113+
"${JSBSIM_SHARED_LIB}"
114+
"$<TARGET_FILE_DIR:${TARGET_NAME}>"
115+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
116+
"${RUNTIME_ONNX_SHARED_LIB}"
117+
"$<TARGET_FILE_DIR:${TARGET_NAME}>"
118+
COMMENT "Copying Project AirSim Runtime shared-library dependencies"
119+
)

0 commit comments

Comments
 (0)