Skip to content

Commit

Permalink
Basic setup for Python interface using SWIG (#216)
Browse files Browse the repository at this point in the history
* Basic setup for Python interface using SWIG
* Changes in cmake to run python tests with colcon
* Adding Vector3 and Vector4 python interface tests
* Changing from a single ign_math.i file to a ruby.i and a python.i

Signed-off-by: Marcos Wagner <[email protected]>

* Fixes install path and name math instead of pymath.
* Adds py examples
* Fixes ruby_TEST set up.
* Support both Swig3 and Swig4 versions.

Signed-off-by: Franco Cipollone <[email protected]>

* Adds line in the src/CMakeLists.txt file to solve CI error.
Adds python test files to some of the classes that had an associated .i file.

Signed-off-by: LolaSegura <[email protected]>

* Remove Marya from CODEOWNERS (#217)

Signed-off-by: Michael Carroll <[email protected]>

* Use FAKE_INSTALL_PREFIX for PYTHONPATH in tests and
move the variable definition to the root CMakeLists.txt
* Set LD_LIBRARY_PATH for python tests
Point to fake install lib folder.

Signed-off-by: Steve Peters <[email protected]>

Co-authored-by: Franco Cipollone <[email protected]>
Co-authored-by: LolaSegura <[email protected]>
Co-authored-by: Louise Poubel <[email protected]>
Co-authored-by: Michael Carroll <[email protected]>
Co-authored-by: Steve Peters <[email protected]>
  • Loading branch information
6 people authored Aug 17, 2021
1 parent cf6d052 commit 2330061
Show file tree
Hide file tree
Showing 17 changed files with 1,368 additions and 15 deletions.
13 changes: 13 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,21 @@ if (SWIG_FOUND)
else()
message (STATUS "Searching for Ruby - found.")
endif()

########################################
# Include python
find_package(PythonLibs QUIET)
if (NOT PYTHONLIBS_FOUND)
message (STATUS "Searching for Python - not found.")
else()
message (STATUS "Searching for Python - found.")
endif()
endif()

# Location of "fake install folder" used in tests
# Defined here at root scope so it is available for tests in src and test folders
set(FAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/fake/install")

#============================================================================
# Configure the build
#============================================================================
Expand Down
43 changes: 43 additions & 0 deletions examples/angle_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright (C) 2021 Open Source Robotics Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This example will only work if the Python interface library was compiled and
# installed.
#
# Modify the PYTHONPATH environment variable to include the ignition math
# library install path. For example, if you install to /user:
#
# $ export PYTHONPATH=/usr/lib/python:$PYTHONPATH
#

import ignition.math

print("PI in degrees = {}\n".format(ignition.math.Angle.Pi.Degree()))

a1 = ignition.math.Angle(1.5707)
a2 = ignition.math.Angle(0.7854)
print("a1 = {} radians, {} degrees\n".format(a1.Radian(), a1.Degree()))
print("a2 = {} radians, {} degrees\n".format(a2.Radian(), a2.Degree()))
print("a1 * a2 = {} radians, {} degrees\n".format((a1 * a2).Radian(),
(a1 * a2).Degree()))
print("a1 + a2 = {} radians, {} degrees\n".format((a1 + a2).Radian(),
(a1 + a2).Degree()))
print("a1 - a2 = {} radians, {} degrees\n".format((a1 - a2).Radian(),
(a1 - a2).Degree()))

a3 = ignition.math.Angle(15.707)
print("a3 = {} radians, {} degrees\n".format(a3.Radian(), a3.Degree()))
a3.Normalize()
print("a3.Normalize = {} radians, {} degrees\n".format(a3.Radian(),
a3.Degree()))
39 changes: 39 additions & 0 deletions examples/vector2_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright (C) 2021 Open Source Robotics Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This example will only work if the Python interface library was compiled and
# installed.
#
# Modify the PYTHONPATH environment variable to include the ignition math
# library install path. For example, if you install to /user:
#
# $ export PYTHONPATH=/usr/lib/python:$PYTHONPATH
#
import ignition.math

va = ignition.math.Vector2d(1, 2)
vb = ignition.math.Vector2d(3, 4)
vc = ignition.math.Vector2d(vb)

print("va = {} {}\n".format(va.X(), va.Y()))
print("vb = {} {}\n".format(vb.X(), vb.Y()))
print("vc = {} {}\n".format(vc.X(), vc.Y()))

vb += va
print("vb += va: {} {}\n".format(vb.X(), vb.Y()))

vb.Normalize()
print("vb.Normalize = {} {}\n".format(vb.X(), vb.Y()))

print("vb.Distance(va) = {}\n".format(vb.Distance(va)))
34 changes: 34 additions & 0 deletions examples/vector3_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright (C) 2021 Open Source Robotics Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This example will only work if the Python interface library was compiled and
# installed.
#
# Modify the PYTHONPATH environment variable to include the ignition math
# library install path. For example, if you install to /user:
#
# $ export PYTHONPATH=/usr/lib/python:$PYTHONPATH
#
import ignition.math

v1 = ignition.math.Vector3d(0, 0, 3)
print("v =: {} {} {}\n".format(v1.X(), v1.Y(), v1.Z()))

v2 = ignition.math.Vector3d(4, 0, 0)
print("v2 = {} {} {}\n".format(v2.X(), v2.Y(), v2.Z()))

v3 = v1 + v2
print("v1 + v2 = {} {} {}\n".format(v3.X(), v3.Y(), v3.Z()))

print("v1.Distance(v2) = {}\n".format(v1.Distance(v2)))
90 changes: 90 additions & 0 deletions src/Angle_TEST.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Copyright (C) 2021 Open Source Robotics Foundation

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest
import math
from ignition.math import Angle


class TestAngle(unittest.TestCase):

def test_angle(self):

angle1 = Angle()
self.assertEqual(0.0, angle1.Radian())

angle1.SetDegree(90.0)
self.assertTrue(angle1 == Angle.HalfPi)

angle1.SetDegree(180.0)
self.assertTrue(angle1 == Angle.Pi)
self.assertFalse(angle1 == Angle.Pi + Angle(0.1))
self.assertTrue(angle1 == Angle.Pi + Angle(0.0001))
self.assertTrue(angle1 == Angle.Pi - Angle(0.0001))
self.assertTrue(Angle(0) == Angle(0))
self.assertTrue(Angle(0) == Angle(0.001))

angle1 = Angle(0.1) - Angle(0.3)
self.assertAlmostEqual(angle1.Radian(), -0.2)

angle = Angle(0.5)
self.assertEqual(0.5, angle.Radian())

angle.SetRadian(math.pi/2)
self.assertAlmostEqual(math.degrees(math.pi/2), angle.Degree())

angle.SetRadian(math.pi)
self.assertAlmostEqual(math.degrees(math.pi), angle.Degree())

def test_normalized_angles(self):

angle = Angle(Angle.Pi)
normalized = angle.Normalized()

angle.Normalized()
self.assertEqual(math.degrees(math.pi), angle.Degree())
self.assertEqual(normalized, angle)

def test_angle_operations(self):

angle = Angle(0.1) + Angle(0.2)
self.assertAlmostEqual(0.3, angle.Radian())

angle = Angle(0.1) * Angle(0.2)
self.assertAlmostEqual(0.02, angle.Radian())

angle = Angle(0.1) / Angle(0.2)
self.assertAlmostEqual(0.5, angle.Radian())

angle -= Angle(0.1)
self.assertAlmostEqual(0.4, angle.Radian())

angle += Angle(0.2)
self.assertAlmostEqual(0.6, angle.Radian())

angle *= Angle(0.5)
self.assertAlmostEqual(0.3, angle.Radian())

angle /= Angle(0.1)
self.assertAlmostEqual(3.0, angle.Radian())
self.assertTrue(angle == Angle(3))
self.assertTrue(angle != Angle(2))
self.assertTrue(angle < Angle(4))
self.assertTrue(angle > Angle(2))
self.assertTrue(angle >= Angle(3))
self.assertTrue(angle <= Angle(3))


if __name__ == '__main__':
unittest.main()
87 changes: 81 additions & 6 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ if (SWIG_FOUND)
set(CMAKE_SWIG_FLAGS "")

include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${PYTHON_INCLUDE_PATH})

set(swig_files
ruby
Angle
GaussMarkovProcess
Rand
Expand All @@ -50,27 +50,32 @@ if (RUBY_FOUND)
# Generate the list if .i files
list(APPEND swig_i_files ${swig_file}.i)
endforeach()
list(APPEND ruby_tests ruby_TEST)

# Turn on c++
set_source_files_properties(${swig_i_files} PROPERTIES CPLUSPLUS ON)
set_source_files_properties(${swig_i_files} ruby/ruby.i PROPERTIES CPLUSPLUS ON)

# Create the ruby library

set(CMAKE_SWIG_OUTDIR "${CMAKE_BINARY_DIR}/lib/ruby")
if(CMAKE_VERSION VERSION_GREATER 3.8.0)
SWIG_ADD_LIBRARY(math LANGUAGE ruby SOURCES ruby.i ${swig_i_files} ${sources})
SWIG_ADD_LIBRARY(math LANGUAGE ruby SOURCES ruby/ruby.i ${swig_i_files})
else()
SWIG_ADD_MODULE(math ruby ruby.i ${swig_i_files} ${sources})
SWIG_ADD_MODULE(math ruby ruby/ruby.i ${swig_i_files})
endif()

# Suppress warnings on SWIG-generated files
target_compile_options(math PRIVATE
target_compile_options(math PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-Wno-pedantic -Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter>
$<$<CXX_COMPILER_ID:Clang>:-Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter>
$<$<CXX_COMPILER_ID:AppleClang>:-Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter>
)
target_include_directories(math SYSTEM PUBLIC ${RUBY_INCLUDE_DIRS})

SWIG_LINK_LIBRARIES(math ${RUBY_LIBRARY})
SWIG_LINK_LIBRARIES(math
${RUBY_LIBRARY}
ignition-math${PROJECT_VERSION_MAJOR}
)
target_compile_features(math PUBLIC ${IGN_CXX_${c++standard}_FEATURES})
install(TARGETS math DESTINATION ${IGN_LIB_INSTALL_DIR}/ruby/ignition)

Expand All @@ -81,3 +86,73 @@ if (RUBY_FOUND)
--gtest_output=xml:${CMAKE_BINARY_DIR}/test_results/${test}rb.xml)
endforeach()
endif()

#################################
# Create and install Python interfaces
# Example usage
# $ export PYTHONPATH=/ws/install/lib/python/:$PYTHONPATH
if (PYTHONLIBS_FOUND)
set_source_files_properties(python/python.i PROPERTIES CPLUSPLUS ON)
set_source_files_properties(python/python.i PROPERTIES SWIG_FLAGS "-includeall")
set_source_files_properties(python/python.i PROPERTIES SWIG_MODULE_NAME "math")
set(SWIG_PY_LIB pymath)
set(SWIG_PY_LIB_OUTPUT math)

set(CMAKE_SWIG_OUTDIR "${CMAKE_BINARY_DIR}/lib/python")
if(CMAKE_VERSION VERSION_GREATER 3.8.0)
SWIG_ADD_LIBRARY(${SWIG_PY_LIB} LANGUAGE python SOURCES python/python.i)
else()
SWIG_ADD_MODULE(${SWIG_PY_LIB} python python/python.i)
endif()

SWIG_LINK_LIBRARIES(${SWIG_PY_LIB}
${PYTHON_LIBRARIES}
ignition-math${PROJECT_VERSION_MAJOR}
)

# When using SWIG 3 to build a python interface there is an extra underscore between the name of
# the library given to swig_add_library macro and the generated .so
if(NOT ${SWIG_VERSION} VERSION_GREATER 4.0.0)
set(SWIG_PY_LIB "_${SWIG_PY_LIB}")
set(SWIG_PY_LIB_OUTPUT "_${SWIG_PY_LIB_OUTPUT}")
endif()

set_target_properties(${SWIG_PY_LIB}
PROPERTIES
OUTPUT_NAME ${SWIG_PY_LIB_OUTPUT}
)

# Suppress warnings on SWIG-generated files
target_compile_options(${SWIG_PY_LIB} PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-Wno-pedantic -Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter -Wno-cast-function-type -Wno-missing-field-initializers>
$<$<CXX_COMPILER_ID:Clang>:-Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter -Wno-cast-function-type -Wno-missing-field-initializers>
$<$<CXX_COMPILER_ID:AppleClang>:-Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter -Wno-cast-function-type -Wno-missing-field-initializers>
)
install(TARGETS ${SWIG_PY_LIB} DESTINATION ${IGN_LIB_INSTALL_DIR}/python/ignition)
install(FILES ${CMAKE_BINARY_DIR}/lib/python/math.py DESTINATION ${IGN_LIB_INSTALL_DIR}/python/ignition)

if (BUILD_TESTING)
# Add the Python tests
set(python_tests
Angle_TEST
GaussMarkovProcess_TEST
python_TEST
Rand_TEST
Vector2_TEST
Vector3_TEST
Vector4_TEST
)

foreach (test ${python_tests})
add_test(NAME ${test}.py COMMAND
python3 ${CMAKE_SOURCE_DIR}/src/${test}.py)

set(_env_vars)
list(APPEND _env_vars "PYTHONPATH=${FAKE_INSTALL_PREFIX}/lib/python/")
list(APPEND _env_vars "LD_LIBRARY_PATH=${FAKE_INSTALL_PREFIX}/lib:$ENV{LD_LIBRARY_PATH}")
set_tests_properties(${test}.py PROPERTIES
ENVIRONMENT "${_env_vars}")
endforeach()
endif()

endif()
Loading

0 comments on commit 2330061

Please sign in to comment.