Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic setup for Python interface using SWIG #216

Merged
merged 18 commits into from
Aug 17, 2021
Merged
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: 0 additions & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@
# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners

* @scpeters
tutorials/* @maryaB-osr
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()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit for the future (not this pull request): I find f"" strings are more readable than using .format()

for example:

print(f"a2 = {a2.Radian()} radians, {a2.Degree(} degrees\n")

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