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 boilerplate. Place this at the top of your script:
# 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
################################################################-
sys.excepthook: Hooks unhandled exceptions tost.logger_fatal(). When code crashes, the full Python traceback is routed into the active session log in/AppData/Local/SimDynamX/STPro/Logs/rather than failing without diagnostic info. -
st.connect_to_sim(sys.argv): Connects the script process to the simulation server, passing command-line tokens provided byCompute_Server. -
st.leave_sim(): When your script exits gracefully, callst.leave_sim()to disconnect cleanly without closing the simulation for other users.
A Python system accesses its own configuration settings and references to other entities using st.GetThisSystem().
this = st.GetThisSystem()
# Read primitive 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
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 weak pointer (std::weak_ptr) that must be explicitly .lock()ed. In the Python API, this.GetParam(st.VarType.entityRef, ...) handles reference locking internally, returning an active st.Entity instance.
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 bundled Python distribution usingCompute_Server/sc_python_pip.bat:
cd <STP_INSTALL_DIR>\Compute_Server
.\sc_python_pip.bat install package_name