From 1e1988dab175dbf29fb4812d6506664e96959dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Hern=C3=A1ndez=20Cordero?= Date: Thu, 14 Jul 2022 09:23:45 +0200 Subject: [PATCH] Added Region3 Python interface (#450) Signed-off-by: ahcorde --- src/python_pybind11/CMakeLists.txt | 2 + src/python_pybind11/src/Region3.cc | 34 ++++++ src/python_pybind11/src/Region3.hh | 110 +++++++++++++++++ .../src/_ignition_math_pybind11.cc | 3 + src/python_pybind11/test/Region3_TEST.py | 114 ++++++++++++++++++ 5 files changed, 263 insertions(+) create mode 100644 src/python_pybind11/src/Region3.cc create mode 100644 src/python_pybind11/src/Region3.hh create mode 100644 src/python_pybind11/test/Region3_TEST.py diff --git a/src/python_pybind11/CMakeLists.txt b/src/python_pybind11/CMakeLists.txt index 333176750..4d8a074aa 100644 --- a/src/python_pybind11/CMakeLists.txt +++ b/src/python_pybind11/CMakeLists.txt @@ -34,6 +34,7 @@ pybind11_add_module(math SHARED src/Pose3.cc src/Quaternion.cc src/Rand.cc + src/Region3.cc src/RollingMean.cc src/RotationSpline.cc src/SemanticVersion.cc @@ -129,6 +130,7 @@ if (BUILD_TESTING) Pose3_TEST Quaternion_TEST Rand_TEST + Region3_TEST RollingMean_TEST RotationSpline_TEST SemanticVersion_TEST diff --git a/src/python_pybind11/src/Region3.cc b/src/python_pybind11/src/Region3.cc new file mode 100644 index 000000000..6ac3fbd60 --- /dev/null +++ b/src/python_pybind11/src/Region3.cc @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 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. + * +*/ + +#include "Region3.hh" + +namespace ignition +{ +namespace math +{ +namespace python +{ +void defineMathRegion3(py::module &m, const std::string &typestr) +{ + helpDefineMathRegion3(m, typestr + "f"); + helpDefineMathRegion3(m, typestr + "d"); +} + +} // namespace python +} // namespace math +} // namespace ignition diff --git a/src/python_pybind11/src/Region3.hh b/src/python_pybind11/src/Region3.hh new file mode 100644 index 000000000..9ca4cce73 --- /dev/null +++ b/src/python_pybind11/src/Region3.hh @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2022 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. + * +*/ + +#ifndef IGNITION_MATH_PYTHON__REGION3_HH_ +#define IGNITION_MATH_PYTHON__REGION3_HH_ + +#include +#include + +#include +#include + +#include +#include + +namespace py = pybind11; +using namespace pybind11::literals; + +namespace ignition +{ +namespace math +{ +namespace python +{ +/// Help define a pybind11 wrapper for an ignition::math::Region3 +/** + * \param[in] module a pybind11 module to add the definition to + * \param[in] typestr name of the type used by Python + */ +template +void helpDefineMathRegion3(py::module &m, const std::string &typestr) +{ + using Class = ignition::math::Region3; + auto toString = [](const Class &si) { + std::stringstream stream; + stream << si; + return stream.str(); + }; + std::string pyclass_name = typestr; + py::class_(m, + pyclass_name.c_str(), + py::buffer_protocol(), + py::dynamic_attr()) + .def(py::init<>()) + .def(py::init, Interval, Interval>()) + .def(py::self != py::self) + .def(py::self == py::self) + .def("open", + &Class::Open, + "Make an open region") + .def("closed", + &Class::Closed, + "Make a closed region") + .def("ix", + &Class::Ix, + "Get the x-axis interval for the region") + .def("iy", + &Class::Iy, + "Get the y-axis interval for the region") + .def("iz", + &Class::Iz, + "Get the z-axis interval for the region") + .def("empty", + &Class::Empty, + "Check if the interval is empty") + .def("contains", + py::overload_cast &>(&Class::Contains, py::const_), + "Check if the region contains `_point`") + .def("contains", + py::overload_cast(&Class::Contains, py::const_), + "Check if the region contains `_other` region") + .def("intersects", + &Class::Intersects, + "Check if the region intersects `_other` region") + .def("__copy__", [](const Class &self) { + return Class(self); + }) + .def("__deepcopy__", [](const Class &self, py::dict) { + return Class(self); + }, "memo"_a) + .def("__str__", toString) + .def("__repr__", toString); +} + +/// Define a pybind11 wrapper for an ignition::math::Region3 +/** + * \param[in] module a pybind11 module to add the definition to + * \param[in] typestr name of the type used by Python + */ +void defineMathRegion3(py::module &m, const std::string &typestr); + +} // namespace python +} // namespace math +} // namespace ignition + +#endif // IGNITION_MATH_PYTHON__REGION3_HH_ diff --git a/src/python_pybind11/src/_ignition_math_pybind11.cc b/src/python_pybind11/src/_ignition_math_pybind11.cc index 62aa8b57f..a7e623101 100644 --- a/src/python_pybind11/src/_ignition_math_pybind11.cc +++ b/src/python_pybind11/src/_ignition_math_pybind11.cc @@ -44,6 +44,7 @@ #include "Pose3.hh" #include "Quaternion.hh" #include "Rand.hh" +#include "Region3.hh" #include "RollingMean.hh" #include "RotationSpline.hh" #include "SemanticVersion.hh" @@ -136,6 +137,8 @@ PYBIND11_MODULE(math, m) ignition::math::python::defineMathInterval(m, "Interval"); + ignition::math::python::defineMathRegion3(m, "Region3"); + ignition::math::python::defineMathPolynomial3(m, "Polynomial3"); ignition::math::python::defineMathLine2(m, "Line2"); diff --git a/src/python_pybind11/test/Region3_TEST.py b/src/python_pybind11/test/Region3_TEST.py new file mode 100644 index 000000000..45a1e24a3 --- /dev/null +++ b/src/python_pybind11/test/Region3_TEST.py @@ -0,0 +1,114 @@ +# Copyright (C) 2022 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 + +from ignition.math import Intervald, Region3d, Vector3d + + +class TestRegion3(unittest.TestCase): + + def test_default_constructor(self): + region = Region3d() + self.assertTrue(region.ix().empty()) + self.assertTrue(region.iy().empty()) + self.assertTrue(region.iz().empty()) + + def test_constructor(self): + region = Region3d( + Intervald.open(0., 1.), + Intervald.closed(-1., 1.), + Intervald.open(-1., 0.)) + self.assertEqual(region.ix(), Intervald.open(0., 1.)) + self.assertEqual(region.iy(), Intervald.closed(-1., 1.)) + self.assertEqual(region.iz(), Intervald.open(-1., 0.)) + + def test_construction_helpers(self): + openRegion = Region3d.open(0., 0., 0., 1., 1., 1.) + self.assertEqual(openRegion.ix(), Intervald.open(0., 1.)) + self.assertEqual(openRegion.iy(), Intervald.open(0., 1.)) + self.assertEqual(openRegion.iz(), Intervald.open(0., 1.)) + closedRegion = Region3d.closed(0., 0., 0., 1., 1., 1.) + self.assertEqual(closedRegion.ix(), Intervald.closed(0., 1.)) + self.assertEqual(closedRegion.iy(), Intervald.closed(0., 1.)) + self.assertEqual(closedRegion.iz(), Intervald.closed(0., 1.)) + + def test_empty_region(self): + self.assertFalse(Region3d.open(0., 0., 0., 1., 1., 1.).empty()) + self.assertTrue(Region3d.open(0., 0., 0., 0., 0., 0.).empty()) + self.assertTrue(Region3d.open(0., 0., 0., 0., 1., 1.).empty()) + self.assertTrue(Region3d.open(0., 0., 0., 1., 0., 1.).empty()) + self.assertTrue(Region3d.open(0., 0., 0., 1., 1., 0.).empty()) + self.assertFalse(Region3d.closed(0., 0., 0., 0., 0., 0.).empty()) + self.assertTrue(Region3d.closed(1., 1., 1., 0., 0., 0.).empty()) + + def test_region_membership(self): + openRegion = Region3d.open(0., 0., 0., 1., 1., 1.) + self.assertFalse(openRegion.contains(Vector3d(0., 0., 0.))) + self.assertTrue(openRegion.contains(Vector3d(0.5, 0.5, 0.5))) + self.assertFalse(openRegion.contains(Vector3d(1., 1., 1.))) + closedRegion = Region3d.closed(0., 0., 0., 1., 1., 1.) + self.assertTrue(closedRegion.contains(Vector3d(0., 0., 0.))) + self.assertTrue(closedRegion.contains(Vector3d(0.5, 0.5, 0.5))) + self.assertTrue(closedRegion.contains(Vector3d(1., 1., 1.))) + + def test_region_subset(self): + openRegion = Region3d.open(0., 0., 0., 1., 1., 1.) + self.assertTrue(openRegion.contains( + Region3d.open(0.25, 0.25, 0.25, + 0.75, 0.75, 0.75))) + self.assertFalse(openRegion.contains( + Region3d.open(-1., 0.25, 0.25, + 0., 0.75, 0.75))) + self.assertFalse(openRegion.contains( + Region3d.open(0.25, -1., 0.25, + 0.75, 0., 0.75))) + self.assertFalse(openRegion.contains( + Region3d.open(0.25, 0.25, -1., + 0.75, 0.75, 0.))) + self.assertFalse(openRegion.contains( + Region3d.closed(0., 0., 0., + 1., 1., 1.))) + + closedRegion = Region3d.closed(0., 0., 0., 1., 1., 1.) + self.assertTrue(closedRegion.contains( + Region3d.closed(0., 0., 0., 1., 1., 1.))) + self.assertTrue(closedRegion.contains( + Region3d.closed(0., 0., 0., 0., 0., 0.))) + + def test_region_equality(self): + self.assertNotEqual( + Region3d.open(0., 0., 0., 0., 0., 0.), + Region3d.open(0., 0., 0., 0., 0., 0.)) + self.assertEqual( + Region3d.closed(0., 0., 0., 0., 0., 0.), + Region3d.closed(0., 0., 0., 0., 0., 0.)) + self.assertNotEqual( + Region3d.open(0., 0., 0., 1., 1., 1.), + Region3d.closed(0., 0., 0., 1., 1., 1.)) + + def test_region_intersection(self): + region = Region3d.open(0., 0., 0., 1., 1., 1.) + self.assertTrue(region.intersects( + Region3d.open(0.5, 0.5, 0.5, 1.5, 1.5, 1.5))) + self.assertTrue(region.intersects( + Region3d.open(-0.5, -0.5, -0.5, 0.5, 0.5, 0.5))) + self.assertFalse(region.intersects( + Region3d.open(1., 1., 1., 2., 2., 2.))) + self.assertFalse(region.intersects( + Region3d.open(-1., -1., -1., 0., 0., 0.))) + + +if __name__ == '__main__': + unittest.main()