Introduction

The Python Bindings panel allows you to run custom Python code inside SatModeler’s simulation loop, at spacecraft level. This is the main extension mechanism in SatModeler: it lets you add behavior that is not provided by built-in models, without modifying the simulator itself.

In the current version, each spacecraft exposes four binding slots:

  • External Force 1 → returns a force vector [Fx, Fy, Fz] in N
  • External Force 2 → returns a force vector [Fx, Fy, Fz] in N
  • External Torque 1 → returns a torque vector [Tx, Ty, Tz] in N·m
  • External Torque 2 → returns a torque vector [Tx, Ty, Tz] in N·m

These four slots are intentionally generic. You can use them to apply real physical effects (thrusters, magnetic torquers, reaction wheels proxy models, deployables, custom drag/lift, custom SRP, etc.), but also to simulate onboard subsystems such as:

  • Sensors and estimation pipelines
  • Actuator logic and control laws
  • OBC (on-board computer) decision making
  • Power generation / battery models
  • Custom telemetry logging and fault injection

Even if your subsystem does not naturally produce a force or torque, you can still run it in the binding and return a zero vector.

How bindings are executed (important)

SatModeler uses a Runge–Kutta 4 (RK4) numerical integrator.

As a result:

  • Each enabled Python binding is called four times per solver time step (Δt).

This behavior is expected and correct.

Practical implications:

  • Avoid heavy computation and excessive printing inside bindings.
  • If logic should update only once per solver step, implement gating or caching based on time.

Main workflow

Create Python File

Clicking Create Python File generates a ready-to-edit .py file containing four default functions:

  • fcn_ext_force_1
  • fcn_ext_force_2
  • fcn_ext_torque_1
  • fcn_ext_torque_2

The file and function names are automatically linked to the corresponding binding slots.

By default, all functions return [0.0, 0.0, 0.0], so the simulation behavior is unchanged until custom logic is added.

Linking your own file and functions

Each binding slot contains:

  • Path — the .py file containing your function
  • Function Name — the Python function to be called

This allows flexible project organization, for example:

  • One shared file used by multiple spacecraft
  • One file per spacecraft
  • One file per subsystem (ADCS, power, payload, etc.)

Required function signature

Each slot calls a Python function with the signature:

def my_function(SimData):
    return [x, y, z]
  • SimData is the simulation context passed by SatModeler (explained in detail below).
  • The function must return a 3-element vector.

Each function return has units. These are:

  • External Force 1 and 2 must return [Fx, Fy, Fz] in Newtons (N)
  • External Torque 1 and 2 must return [Tx, Ty, Tz] in Newton-meters (N·m)

If you do not want to apply any force/torque but still want to run code, return [0.0, 0.0, 0.0].

Using your own Python classes

Bindings are standard Python. You can define classes, instantiate them once, and reuse them on every call.

A common pattern is:

  • Define a class representing a subsystem (e.g., a sensor, controller, power system)
  • Create a single global instance of that class
  • Use that instance inside the binding function

Example:

import numpy as np

class BDotController:
    def __init__(self, gain):
        self.gain = gain
        self.last_t = None
        self.cached_torque = np.zeros(3)

    def update_once_per_step(self, SimData):
        # Gate updates so we only compute once per solver step (even though RK4 calls 4 times)
        t = SimData.elapsed_time
        if self.last_t == t:
            return self.cached_torque

        self.last_t = t
        sc = SimData.Spacecraft

        w = np.array(sc.angular_velocity_BF)     # rad/s
        B = np.array(sc.magnetic_field_BF)       # T

        # Illustrative example: compute a torque direction based on w and B
        # (Your own model / units strategy applies)
        self.cached_torque = -self.gain * np.cross(w, B)
        return self.cached_torque

controller = BDotController(gain=1e-3)

def fcn_ext_torque_1(SimData):
    T = controller.update_once_per_step(SimData)
    return T.tolist()

This approach is ideal for modeling sensors/actuators/OBC/power because you can store internal state (filters, integrators, counters, memory, etc.) inside your class.

SimData

What is SimData?

SimData is a data container SatModeler passes to every binding call. It gives your Python function access to:

  • The current simulation time and step size
  • The current UTC simulation date
  • The environment (Earth, Sun, Moon, planets)
  • The spacecraft that owns this binding
  • All spacecraft in the mission
Category Python field What it is Units / type
Solver SimData.elapsed_time Elapsed simulation time since start s
SimData.delta_time Solver step size (Δt) s
SimData.maximum_simulation_time Maximum simulation duration s
SimData.current_simulation_date_UTC Current simulation date/time in UTC GregorianDate
Spacecraft SimData.Spacecraft The spacecraft associated with this binding call Spacecraft
SimData.all_spacecraft All spacecraft in the mission (includes SimData.Spacecraft) list of Spacecraft
Environment SimData.Earth Earth model used in the simulation Earth
SimData.Sun Sun model used in the simulation Sun
SimData.Moon Moon model used in the simulation Moon
SimData.Mercury Mercury model CelestialBody
SimData.Venus Venus model CelestialBody
SimData.Mars Mars model CelestialBody
SimData.Jupiter Jupiter model CelestialBody
SimData.Saturn Saturn model CelestialBody
SimData.Uranus Uranus model CelestialBody
SimData.Neptune Neptune model CelestialBody
SimData.Pluto Pluto model CelestialBody

Accessing the Simulation date (GregorianDate)

SimData.current_simulation_date_UTC exposes:

  • SimData.current_simulation_date_UTC.year
  • SimData.current_simulation_date_UTC.month
  • SimData.current_simulation_date_UTC.day
  • SimData.current_simulation_date_UTC.hour
  • SimData.current_simulation_date_UTC.minute
  • SimData.current_simulation_date_UTC.second

Example:

utc = SimData.current_simulation_date_UTC
print(utc.year, utc.month, utc.day, utc.hour, utc.minute, utc.second)

Environment objects

CelestialBody (generic planets)

Planets are exposed as CelestialBody. You can read:

  • body.grav_param — gravitational parameter μ [m³/s²]
  • body.name — name of the body
  • body.pos_ICRF_from_solar_sys_bar — ICRF position w.r.t Solar System barycenter [m]

Example:

mars = SimData.Mars
mu = mars.grav_param
r_icrf = mars.pos_ICRF_from_solar_sys_bar

Earth-specific fields

In addition to CelestialBody fields, Earth exposes:

  • SimData.Earth.mass [kg]
  • SimData.Earth.angular_velocity [rad/s]
  • SimData.Earth.C_ECI_ECEF — ECEF→ECI rotation matrix
  • SimData.Earth.q_ECI_ECEF — quaternion form of that transform
  • SimData.Earth.determine_mag_field_ECI(lat_deg, lon_deg, h_ellip, dec_year) → returns B in ECI [T]

Sun-specific fields

  • SimData.Sun.pos_ECI [m]
  • SimData.Sun.unit_pos_ECI
  • SimData.Sun.right_ascension [rad]
  • SimData.Sun.declination [rad]

Moon-specific fields

  • SimData.Moon.pos_ICRF_from_Earth [m]

Spacecraft object

Category Python field What it is Units / type
Metadata sc.name Spacecraft name string
Physical sc.mass Spacecraft mass kg
sc.inertia_matrix_BF Inertia matrix in body frame kg·m²
Environment at S/C sc.atmospheric_density Atmospheric density at current position kg/m³
sc.magnetic_field_BF Earth magnetic field at S/C, in body frame T
sc.eclipse_status Eclipse state (sunlight/penumbra/umbra/annular) EclipseType
Translational sc.pos_ECI Position vector in ECI m
sc.vel_ECI Velocity vector in ECI m/s
sc.pos_ECEF Position vector in ECEF m
sc.vel_ECEF Velocity vector in ECEF m/s
sc.lat_gd Geodetic latitude rad
sc.lon Longitude rad
sc.h_ellip Altitude above reference ellipsoid m
sc.orbital_elements Keplerian elements derived from current state KeplerianElements
Attitude sc.angular_velocity_BF Angular velocity in body frame rad/s
sc.quaternion_BF_ECI Body w.r.t ECI quaternion Quaternion
sc.rotation_matrix_BF_ECI Body w.r.t ECI rotation matrix 3×3 matrix
Perturbations sc.gravity_acc_ECI Gravity acceleration in ECI m/s²
sc.third_body_acc_ECI Total third-body acceleration in ECI m/s²
sc.solar_pressure_acc_ECI Solar pressure acceleration in ECI m/s²
sc.solar_pressure_torque_BF Solar pressure torque in body frame N·m
sc.aerodynamic_acc_ECI Aerodynamic acceleration in ECI m/s²
sc.aerodynamic_torque_BF Aerodynamic torque in body frame N·m
sc.magnetic_torque_BF Magnetic torque in body frame N·m
sc.gravity_gradient_torque_BF Gravity-gradient torque in body frame N·m

KeplerianElements fields

kep = sc.orbital_elements

  • kep.p — semi-parameter [m]
  • kep.a — semi-major axis [m]
  • kep.e — eccentricity
  • kep.i — inclination [rad]
  • kep.right_asc — RAAN [rad]
  • kep.arg_of_perigee — argument of perigee [rad]
  • kep.true_anom — true anomaly [rad]
  • kep.true_arg_of_perigee — extended element [rad]
  • kep.arg_of_lat — extended element [rad]
  • kep.true_lon — extended element [rad]

Quaternion fields

If you use sc.quaternion_BF_ECI, you can access:

  • q.x(), q.y(), q.z(), q.w()
  • q.coeffs() → returns [x, y, z, w]

Minimal “start here” example

import numpy as np

def fcn_ext_force_1(SimData):
    sc = SimData.Spacecraft

    t = SimData.elapsed_time
    r = np.array(sc.pos_ECI)
    v = np.array(sc.vel_ECI)

    # Do whatever logic you want here...
    # Return a real force, or return zero
    return [0.0, 0.0, 0.0]
Scroll to Top