Repository navigation
Programming STP Sims with Python
| The Fundamentals → Programming STP Sims with Python |
|---|
Written by Neil McHenry.
Prerequisites:
Note
This page explains how to author and run custom Python systems that interact with live simulations. For initial state configuration and JSON syntax, review The Sim Config. For parameter typing details, review The Parameter.
In Space Teams PRO, simulation tasks are organized cleanly into State and Behavior:
-
Entities (State): Everything that exists spatially in a simulation (spacecraft, celestial bodies, robotic arms, sensor masts) is an Entity. Entities store their state as parameters in a
ParamMap. Any parameter change on an Entity is replicated across the network to all connected clients. - Systems (Behavior): Logic that evaluates algorithms, calculates mathematical equations, reads inputs, and drives Entity parameters over time.
┌────────────────────────────────────────────────────────┐
│ Space Teams PRO │
│ │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ Rover Entity │ │ Planet Entity │ │
│ │ (Location, State) │ │ (BodyFixedFrame) │ │
│ └─────────▲──────────┘ └─────────▲──────────┘ │
│ │ │ │
│ Param Access Frame Query │
│ │ │ │
│ ┌─────────┴───────────────────────────┴──────────┐ │
│ │ Python System Process │ │
│ │ - Reads Config Inputs │ │
│ │ - Steps Kinematic Loop │ │
│ │ - Updates Entity Location & Rotation │ │
│ │ - Emits HUD Alerts and Logs │ │
│ └────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
Systems can be written as compiled C++ shared libraries (.dll or .so) or as standalone Python scripts (.py). When a simulation starts, Compute_Server launches configured Python systems as separate processes and connects them directly to the simulation runtime.
Important
Replication Rule: Only Entities have automatic parameter replication across the network. System instance parameters do not replicate. Systems are designed to execute locally, updating entity parameters that the platform core then synchronizes across all users.
Every Space Teams Python system must open with the standard connection and sim start boilerplate. Follow the instructions here on making a new Python System then come back.
Here is the top section of Python Template boilerplate code from v0.54.2:
import os, sys, time, datetime, traceback
import spaceteams as st
import numpy as np
# Can import other modules here, but any code that actually executes before st.connect_to_sim()
# will have less helpful logging and error handling. SimGlobals and logger functions are not yet available.
def before_init():
pass # Replace this to run code during the Sync state that needs to finish before Init starts anywhere in the sim.
# Sync state is where Sim Editing is done, so code in here shouldn't change parameters that would
# make saving and reloading the sim problematic.
def before_runtime():
pass # Replace this to run code during the Init state that needs to finish before Runtime starts anywhere in the sim.
#################################################################
# DON'T CHANGE ANY OF THE BELOW; NECESSARY FOR JOINING SIMULATION
def custom_exception_handler(exctype, value, tb):
error_message = "".join(traceback.format_exception(exctype, value, tb))
st.logger_fatal(error_message)
exit(1)
sys.excepthook = custom_exception_handler
st.BeforeInit(before_init)
st.BeforeRuntime(before_runtime)
st.connect_to_sim(sys.argv)
# DON'T CHANGE ANY OF THE ABOVE; NECESSARY FOR JOINING SIMULATION
#################################################################This boilerplate code is explained in the Make New Python System wiki page. Once the code has passed the st.connect_to_sim line, it is part of the running simulation.
A Python system accesses its own configuration settings and references to specific entities using st.GetThisSystem().
this = st.GetThisSystem()
# Read per-system-instance parameters configured in the SimConfig
loop_rate = this.GetParam(st.VarType.double, "LoopFreqHz")
drive_speed = this.GetParam(st.VarType.double, "DriveSpeed")
enable_feature = this.GetParam(st.VarType.bool, "EnableFeature")
# Retrieve Entity references from instance parameters in the SimConfig
rover_entity: st.Entity = this.GetParam(st.VarType.entityRef, "TargetEntity")
planet_entity: st.Entity = this.GetParam(st.VarType.entityRef, "PlanetRef")| Enum | Python Type | Description |
|---|---|---|
st.VarType.bool |
bool |
Boolean flag (True or False) |
st.VarType.int32 |
int |
32-bit signed integer |
st.VarType.double |
float |
Standard 64-bit floating point number |
st.VarType.string |
str |
Text string |
st.VarType.doubleV3 |
np.ndarray (3,) |
3D coordinate vector [x, y, z]
|
st.VarType.doubleV4 |
np.ndarray (4,) |
4D vector or quaternion [x, y, z, w]
|
st.VarType.entityRef |
st.Entity |
Reference pointer to a simulation entity |
Tip
In C++, EntityRef parameters return a SC_EntityRef object that must be .lock()ed to access the Entity inside it. In the Python API, this.GetParam(st.VarType.entityRef, ...) handles reference locking internally, returning an owning st.Entity reference. This has special significance when entities are deleted, but their owning references keep their data alive.
Space Teams uses coordinate reference frames to manage parent-child spatial relationships. To position an entity, you work relative to a target entity's body-fixed frame.
# Obtain reference frames
planet_frame = planet_entity.GetBodyFixedFrame()
rover_frame = rover_entity.GetBodyFixedFrame()
# Retrieve location expressed in the planet's coordinate frame
current_loc = rover_entity.getLocation().WRT_ExprIn(planet_frame)
print(f"Current coordinates (m): X={current_loc[0]}, Y={current_loc[1]}, Z={current_loc[2]}")To translate an entity through space, compute the target coordinates and apply them using one of the following methods:
# Option A: Direct Location Update (Kinematic Teleport / Prescribed Motion)
new_loc = current_loc + np.array([5.0, 0.0, 0.0])
rover_entity.setLocation(st.frames.FramedLoc(new_loc, planet_frame))
# Option B: Set Location with Zero Residual Velocity
rover_entity.setLocation_ZeroVel(st.frames.FramedLoc(new_loc, planet_frame))
# Option C: Set Location, Velocity, and Acceleration Simultaneously
vel_vector = np.array([2.5, 0.0, 0.0])
acc_vector = np.array([0.0, 0.0, 0.0])
rva_data = st.frames.rva_struct(new_loc, vel_vector, acc_vector)
rover_entity.setLocVelAcc(st.frames.FramedLocVelAcc(rva_data, planet_frame))Orientations are applied using FramedRot objects constructed from quaternions or Direction Cosine Matrices (DCM):
# Construct a rotation matrix (3x3 DCM) or quaternion
dcm_matrix = np.identity(3)
rover_entity.setRotation(st.frames.FramedRot(dcm_matrix, planet_frame))Space Teams provides built-in mechanisms to present information directly to users inside the simulation viewport:
# On-Screen Alert: Temporary banner across user viewports (auto-dismisses)
st.OnScreenAlert(
"Target waypoint reached!",
"NavigationTopic",
st.Severity.Info
)
# On-Screen Log Message: Appends entry into persistent HUD message feed
st.OnScreenLogMessage(
"Thruster subsystem initialized.",
"Subsystems",
st.Severity.Info
)
# Logger output to Compute_Server console and session logs
st.logger_info("Step completed successfully.")Severity levels are controlled by the st.Severity enum: Info, Warning, Error, Fatal, and Chat.
Below is a complete, standalone Python system that moves an entity in a horizontal circle around an origin point while logging progress:
# DON'T CHANGE ANY OF THE BELOW; NECESSARY FOR JOINING SIMULATION
import os, sys, time, datetime, traceback
import spaceteams as st
import numpy as np
def custom_exception_handler(exctype, value, tb):
error_message = "".join(traceback.format_exception(exctype, value, tb))
st.logger_fatal(error_message)
sys.exit(1)
sys.excepthook = custom_exception_handler
st.connect_to_sim(sys.argv)
# DON'T CHANGE ANY OF THE BELOW; NECESSARY FOR JOINING SIMULATION
################################################################
st.OnScreenLogMessage("CircularMotionSystem starting...", "Navigation", st.Severity.Info)
# 1. Grab configuration parameters and entities
this = st.GetThisSystem()
target_entity: st.Entity = this.GetParam(st.VarType.entityRef, "ControlledEntity")
center_ref: st.Entity = this.GetParam(st.VarType.entityRef, "CenterFrameEntity")
reference_frame = center_ref.GetBodyFixedFrame()
# Motion parameters
radius = 25.0 # Orbit radius (meters)
angular_speed = 0.2 # Yaw rotation speed (rad/s)
loop_freq = 50.0 # Target frequency (Hz)
dt = 1.0 / loop_freq
st.OnScreenAlert("Entity autonomous movement engaged.", "Motion", st.Severity.Info)
theta = 0.0
while True:
# 2. Compute circular trajectory coordinates
x = radius * np.cos(theta)
y = radius * np.sin(theta)
z = 0.0 # Planar elevation
# 3. Compute tangent velocity vector
vx = -radius * angular_speed * np.sin(theta)
vy = radius * angular_speed * np.cos(theta)
vz = 0.0
new_pos = np.array([x, y, z])
vel_vec = np.array([vx, vy, vz])
acc_vec = np.array([0.0, 0.0, 0.0])
# 4. Commit kinematic update to entity
kinematic_state = st.frames.rva_struct(new_pos, vel_vec, acc_vec)
target_entity.setLocVelAcc(st.frames.FramedLocVelAcc(kinematic_state, reference_frame))
# 5. Advance phase angle and maintain execution pacing
theta += angular_speed * dt
if theta >= 2.0 * np.pi:
theta -= 2.0 * np.pi
time.sleep(dt)To run your Python script within a simulation, register it inside your .simconf file under SystemManager -> Systems:
{
"Nametag": "CircularMotionSystem",
"Source": "Local/Repos/MyRepo/CircularMotionSystem.py",
"Type": "Python",
"Active": true,
"Frequency": 50.0,
"Inst_Parameters": {
"ControlledEntity": [ "EntityRef", "Excavator_1" ],
"CenterFrameEntity": [ "EntityRef", "Moon" ],
"DriveSpeed": 2.5
}
}
-
Nametag: Unique system identifier. -
Source: Path to your Python file (relative to your installation or local assets directory). -
Type: Must be set to"Python". -
Inst_Parameters: Defines initial parameters queried bythis.GetParam()inside your script.
-
Always Include Loop Sleeping: Ensure your update loop includes
time.sleep(1.0 / frequency). Without pacing, Python will consume 100% of a CPU core. -
Throttle Screen Messages: Do not call
st.OnScreenAlert()orst.OnScreenLogMessage()every frame in high-frequency loops; rate-limit notifications to avoid filling HUD message queues. -
Installing Third-Party Libraries: If your system imports external packages (like
scipyorpandas), install them into Space Teams PRO's automatically generated virtual environment.- On Windows:
Compute_Server/st_pip.bat:cd <STP_INSTALL_DIR>\Compute_Server .\st_pip.bat install package_name
- On Windows:
- See the Programming STP Sims with Python tutorial.
- Consult the Python API Docs for functionality details.