Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions PythonClient/airsim/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Unit tests for airsim Python package (no simulator required).
64 changes: 64 additions & 0 deletions PythonClient/airsim/tests/test_types_numpy2_scalars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""Vector3r/Quaternionr scalar ops must work under NumPy 2 (no np.sctypes)."""
import importlib.util
import os
import sys
import unittest

import numpy as np

_HERE = os.path.dirname(os.path.abspath(__file__))
_TYPES = os.path.join(os.path.dirname(_HERE), "types.py")


def _load_types():
spec = importlib.util.spec_from_file_location("airsim_types_under_test", _TYPES)
mod = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = mod
spec.loader.exec_module(mod)
return mod


_types = _load_types()
Vector3r = _types.Vector3r
Quaternionr = _types.Quaternionr


class TestNumpy2Scalars(unittest.TestCase):
def test_vector3_mul_python_float(self):
v = Vector3r(1.0, 2.0, 3.0) * 2.0
self.assertEqual((v.x_val, v.y_val, v.z_val), (2.0, 4.0, 6.0))

def test_vector3_mul_numpy_float32(self):
v = Vector3r(1.0, -1.0, 0.5) * np.float32(2.0)
self.assertAlmostEqual(float(v.x_val), 2.0, places=5)
self.assertAlmostEqual(float(v.y_val), -2.0, places=5)
self.assertAlmostEqual(float(v.z_val), 1.0, places=5)

def test_vector3_div_numpy_int64(self):
v = Vector3r(2.0, 4.0, 6.0) / np.int64(2)
self.assertEqual(
(float(v.x_val), float(v.y_val), float(v.z_val)),
(1.0, 2.0, 3.0),
)

def test_vector3_mul_rejects_str(self):
with self.assertRaises(TypeError):
_ = Vector3r(1, 2, 3) * "2"

def test_quaternion_div_float(self):
q = Quaternionr(2.0, 4.0, 6.0, 8.0) / 2.0
self.assertEqual(
(q.x_val, q.y_val, q.z_val, q.w_val),
(1.0, 2.0, 3.0, 4.0),
)

def test_quaternion_div_numpy_uint(self):
q = Quaternionr(2.0, 4.0, 6.0, 8.0) / np.uint32(2)
self.assertEqual(
(float(q.x_val), float(q.y_val), float(q.z_val), float(q.w_val)),
(1.0, 2.0, 3.0, 4.0),
)


if __name__ == "__main__":
unittest.main()
20 changes: 16 additions & 4 deletions PythonClient/airsim/types.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
from __future__ import print_function
import msgpackrpc #install as admin: pip install msgpack-rpc-python
try:
import msgpackrpc # install as admin: pip install msgpack-rpc-python
except ImportError: # allow math type unit tests without full RPC stack
msgpackrpc = None
import numpy as np #pip install numpy
import math

def _is_real_number(value):
"""True for Python/NumPy real scalars (NumPy 2 removed np.sctypes)."""
if isinstance(value, (int, float)):
return True
if isinstance(value, (np.integer, np.floating)):
return True
return False


class MsgpackMixin:
def __repr__(self):
from pprint import pformat
Expand Down Expand Up @@ -110,13 +122,13 @@ def __sub__(self, other):
return Vector3r(self.x_val - other.x_val, self.y_val - other.y_val, self.z_val - other.z_val)

def __truediv__(self, other):
if type(other) in [int, float] + np.sctypes['int'] + np.sctypes['uint'] + np.sctypes['float']:
if _is_real_number(other):
return Vector3r( self.x_val / other, self.y_val / other, self.z_val / other)
else:
raise TypeError('unsupported operand type(s) for /: %s and %s' % ( str(type(self)), str(type(other))) )

def __mul__(self, other):
if type(other) in [int, float] + np.sctypes['int'] + np.sctypes['uint'] + np.sctypes['float']:
if _is_real_number(other):
return Vector3r(self.x_val*other, self.y_val*other, self.z_val*other)
else:
raise TypeError('unsupported operand type(s) for *: %s and %s' % ( str(type(self)), str(type(other))) )
Expand Down Expand Up @@ -188,7 +200,7 @@ def __mul__(self, other):
def __truediv__(self, other):
if type(other) == type(self):
return self * other.inverse()
elif type(other) in [int, float] + np.sctypes['int'] + np.sctypes['uint'] + np.sctypes['float']:
elif _is_real_number(other):
return Quaternionr( self.x_val / other, self.y_val / other, self.z_val / other, self.w_val / other)
else:
raise TypeError('unsupported operand type(s) for /: %s and %s' % ( str(type(self)), str(type(other))) )
Expand Down