use crate::bodies::dynamic::{DynOrigin, UnknownOriginId, UnknownOriginName};
use crate::bodies::{
Origin, TryMeanRadius, TryPointMass, TryRotationalElements, TrySpheroid, TryTriaxialEllipsoid,
};
use crate::units::python::{PyAngle, PyAngularRate, PyDistance, PyGravitationalParameter};
use lox_core::types::units::Seconds;
use lox_units::{Angle, AngularRate};
use pyo3::create_exception;
use pyo3::exceptions::{PyException, PyTypeError, PyValueError};
use pyo3::prelude::*;
use std::str::FromStr;
create_exception!(
lox_space,
UndefinedOriginPropertyError,
PyException,
"Python exception raised when a body property is not defined for a given origin."
);
pub struct PyUndefinedOriginPropertyError(pub crate::bodies::UndefinedOriginPropertyError);
impl From<PyUndefinedOriginPropertyError> for PyErr {
fn from(err: PyUndefinedOriginPropertyError) -> Self {
UndefinedOriginPropertyError::new_err(err.0.to_string())
}
}
pub struct PyUnknownOriginId(pub UnknownOriginId);
impl From<PyUnknownOriginId> for PyErr {
fn from(err: PyUnknownOriginId) -> Self {
PyValueError::new_err(err.0.to_string())
}
}
struct PyUnknownOriginName(UnknownOriginName);
impl From<PyUnknownOriginName> for PyErr {
fn from(err: PyUnknownOriginName) -> Self {
PyValueError::new_err(err.0.to_string())
}
}
#[pyclass(name = "Origin", module = "lox_space", frozen, eq, from_py_object)]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PyOrigin(pub DynOrigin);
#[pymethods]
impl PyOrigin {
#[new]
fn new(origin: &Bound<'_, PyAny>) -> PyResult<Self> {
if let Ok(origin) = origin.extract::<i32>() {
return Ok(Self(origin.try_into().map_err(PyUnknownOriginId)?));
}
if let Ok(origin) = origin.extract::<&str>() {
return Ok(Self(
DynOrigin::from_str(origin).map_err(PyUnknownOriginName)?,
));
}
Err(PyTypeError::new_err(
"`origin` must be either a string or an integer",
))
}
pub fn __repr__(&self) -> String {
format!("Origin(\"{}\")", self.name())
}
fn __str__(&self) -> &str {
self.name()
}
fn __getnewargs__(&self) -> (&str,) {
(self.name(),)
}
pub fn id(&self) -> i32 {
self.0.id().0
}
pub fn name(&self) -> &'static str {
self.0.name()
}
pub fn gravitational_parameter(&self) -> PyResult<PyGravitationalParameter> {
let gp = self
.0
.try_gravitational_parameter()
.map_err(PyUndefinedOriginPropertyError)?;
Ok(PyGravitationalParameter(gp))
}
pub fn mean_radius(&self) -> PyResult<PyDistance> {
Ok(PyDistance(
self.0
.try_mean_radius()
.map_err(PyUndefinedOriginPropertyError)?,
))
}
pub fn radii(&self) -> PyResult<(PyDistance, PyDistance, PyDistance)> {
let (a, b, c) = self.0.try_radii().map_err(PyUndefinedOriginPropertyError)?;
Ok((PyDistance(a), PyDistance(b), PyDistance(c)))
}
pub fn equatorial_radius(&self) -> PyResult<PyDistance> {
Ok(PyDistance(
self.0
.try_equatorial_radius()
.map_err(PyUndefinedOriginPropertyError)?,
))
}
pub fn polar_radius(&self) -> PyResult<PyDistance> {
Ok(PyDistance(
self.0
.try_polar_radius()
.map_err(PyUndefinedOriginPropertyError)?,
))
}
pub fn rotational_elements(&self, et: Seconds) -> PyResult<(PyAngle, PyAngle, PyAngle)> {
let (ra, dec, rot) = self
.0
.try_rotational_elements(et)
.map_err(PyUndefinedOriginPropertyError)?;
Ok((
PyAngle(Angle::radians(ra)),
PyAngle(Angle::radians(dec)),
PyAngle(Angle::radians(rot)),
))
}
pub fn rotational_element_rates(
&self,
et: Seconds,
) -> PyResult<(PyAngularRate, PyAngularRate, PyAngularRate)> {
let (ra_rate, dec_rate, rot_rate) = self
.0
.try_rotational_element_rates(et)
.map_err(PyUndefinedOriginPropertyError)?;
Ok((
PyAngularRate(AngularRate::radians_per_second(ra_rate)),
PyAngularRate(AngularRate::radians_per_second(dec_rate)),
PyAngularRate(AngularRate::radians_per_second(rot_rate)),
))
}
pub fn right_ascension(&self, et: Seconds) -> PyResult<PyAngle> {
Ok(PyAngle(Angle::radians(
self.0
.try_right_ascension(et)
.map_err(PyUndefinedOriginPropertyError)?,
)))
}
pub fn right_ascension_rate(&self, et: Seconds) -> PyResult<PyAngularRate> {
Ok(PyAngularRate(AngularRate::radians_per_second(
self.0
.try_right_ascension_rate(et)
.map_err(PyUndefinedOriginPropertyError)?,
)))
}
pub fn declination(&self, et: Seconds) -> PyResult<PyAngle> {
Ok(PyAngle(Angle::radians(
self.0
.try_declination(et)
.map_err(PyUndefinedOriginPropertyError)?,
)))
}
pub fn declination_rate(&self, et: Seconds) -> PyResult<PyAngularRate> {
Ok(PyAngularRate(AngularRate::radians_per_second(
self.0
.try_declination_rate(et)
.map_err(PyUndefinedOriginPropertyError)?,
)))
}
pub fn rotation_angle(&self, et: Seconds) -> PyResult<PyAngle> {
Ok(PyAngle(Angle::radians(
self.0
.try_rotation_angle(et)
.map_err(PyUndefinedOriginPropertyError)?,
)))
}
pub fn rotation_rate(&self, et: Seconds) -> PyResult<PyAngularRate> {
Ok(PyAngularRate(AngularRate::radians_per_second(
self.0
.try_rotation_rate(et)
.map_err(PyUndefinedOriginPropertyError)?,
)))
}
}
impl TryFrom<&Bound<'_, PyAny>> for PyOrigin {
type Error = PyErr;
fn try_from(value: &Bound<'_, PyAny>) -> Result<Self, Self::Error> {
if let Ok(origin) = value.extract::<PyOrigin>() {
return Ok(origin);
}
if let Ok(name) = value.extract::<&str>() {
return Ok(Self(
DynOrigin::from_str(name).map_err(PyUnknownOriginName)?,
));
}
if let Ok(id) = value.extract::<i32>() {
return Ok(Self(id.try_into().map_err(PyUnknownOriginId)?));
}
Err(PyTypeError::new_err(
"'origin' argument must be a string, an integer, or an 'Origin' instance",
))
}
}