Skip to content

Commit

Permalink
Added Region3 Python interface (#450)
Browse files Browse the repository at this point in the history
Signed-off-by: ahcorde <[email protected]>
  • Loading branch information
ahcorde authored Jul 14, 2022
1 parent 407afa6 commit 1e1988d
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/python_pybind11/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -129,6 +130,7 @@ if (BUILD_TESTING)
Pose3_TEST
Quaternion_TEST
Rand_TEST
Region3_TEST
RollingMean_TEST
RotationSpline_TEST
SemanticVersion_TEST
Expand Down
34 changes: 34 additions & 0 deletions src/python_pybind11/src/Region3.cc
Original file line number Diff line number Diff line change
@@ -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<float>(m, typestr + "f");
helpDefineMathRegion3<double>(m, typestr + "d");
}

} // namespace python
} // namespace math
} // namespace ignition
110 changes: 110 additions & 0 deletions src/python_pybind11/src/Region3.hh
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <sstream>

#include <pybind11/pybind11.h>
#include <pybind11/operators.h>

#include <ignition/math/Region3.hh>
#include <ignition/math/Vector3.hh>

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<typename T>
void helpDefineMathRegion3(py::module &m, const std::string &typestr)
{
using Class = ignition::math::Region3<T>;
auto toString = [](const Class &si) {
std::stringstream stream;
stream << si;
return stream.str();
};
std::string pyclass_name = typestr;
py::class_<Class>(m,
pyclass_name.c_str(),
py::buffer_protocol(),
py::dynamic_attr())
.def(py::init<>())
.def(py::init<Interval<T>, Interval<T>, Interval<T>>())
.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<const Vector3<T> &>(&Class::Contains, py::const_),
"Check if the region contains `_point`")
.def("contains",
py::overload_cast<const Class &>(&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_
3 changes: 3 additions & 0 deletions src/python_pybind11/src/_ignition_math_pybind11.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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");
Expand Down
114 changes: 114 additions & 0 deletions src/python_pybind11/test/Region3_TEST.py
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 1e1988d

Please sign in to comment.