Skip to content

Commit

Permalink
Add python bindings for sdf::Element and sdf::Param (#1303)
Browse files Browse the repository at this point in the history
Signed-off-by: Addisu Z. Taddese <[email protected]>
  • Loading branch information
azeey authored Aug 22, 2023
1 parent 88e343b commit c7560db
Show file tree
Hide file tree
Showing 14 changed files with 1,620 additions and 4 deletions.
4 changes: 2 additions & 2 deletions include/sdf/Param.hh
Original file line number Diff line number Diff line change
Expand Up @@ -613,12 +613,12 @@ namespace sdf
/// \brief Data type to string mapping
/// \return The type as a string, empty string if unknown type
public: template<typename T>
std::string TypeToString() const;
static std::string TypeToString();
};

///////////////////////////////////////////////
template<typename T>
std::string ParamPrivate::TypeToString() const
std::string ParamPrivate::TypeToString()
{
// cppcheck-suppress syntaxError
if constexpr (std::is_same_v<T, bool>)
Expand Down
6 changes: 6 additions & 0 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pybind11_add_module(${BINDINGS_MODULE_NAME} MODULE
src/sdf/pyCapsule.cc
src/sdf/pyCollision.cc
src/sdf/pyCylinder.cc
src/sdf/pyElement.cc
src/sdf/pyEllipsoid.cc
src/sdf/pyError.cc
src/sdf/pyForceTorque.cc
Expand All @@ -72,13 +73,15 @@ pybind11_add_module(${BINDINGS_MODULE_NAME} MODULE
src/sdf/pyModel.cc
src/sdf/pyNavSat.cc
src/sdf/pyNoise.cc
src/sdf/pyParam.cc
src/sdf/pyParserConfig.cc
src/sdf/pyParticleEmitter.cc
src/sdf/pyPbr.cc
src/sdf/pyPhysics.cc
src/sdf/pyPlane.cc
src/sdf/pyPlugin.cc
src/sdf/pyPolyline.cc
src/sdf/pyPrintConfig.cc
src/sdf/pyProjector.cc
src/sdf/pyRoot.cc
src/sdf/pyScene.cc
Expand Down Expand Up @@ -132,6 +135,7 @@ if (BUILD_TESTING AND NOT WIN32)
pyCapsule_TEST
pyCollision_TEST
pyCylinder_TEST
pyElement_TEST
pyEllipsoid_TEST
pyError_TEST
pyForceTorque_TEST
Expand All @@ -151,13 +155,15 @@ if (BUILD_TESTING AND NOT WIN32)
pyModel_TEST
pyNoise_TEST
pyNavSat_TEST
pyParam_TEST
pyParserConfig_TEST
pyParticleEmitter_TEST
pyPbr_TEST
pyPhysics_TEST
pyPlane_TEST
pyPlugin_TEST
pyPolyline_TEST
pyPrintConfig_TEST
pyProjector_TEST
pyRoot_TEST
pyScene_TEST
Expand Down
8 changes: 8 additions & 0 deletions python/src/sdf/_gz_sdformat_pybind11.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "pyCapsule.hh"
#include "pyCollision.hh"
#include "pyCylinder.hh"
#include "pyElement.hh"
#include "pyEllipsoid.hh"
#include "pyError.hh"
#include "pyExceptions.hh"
Expand All @@ -47,13 +48,15 @@
#include "pyModel.hh"
#include "pyNavSat.hh"
#include "pyNoise.hh"
#include "pyParam.hh"
#include "pyParserConfig.hh"
#include "pyParticleEmitter.hh"
#include "pyPbr.hh"
#include "pyPhysics.hh"
#include "pyPlane.hh"
#include "pyPlugin.hh"
#include "pyPolyline.hh"
#include "pyPrintConfig.hh"
#include "pyProjector.hh"
#include "pyRoot.hh"
#include "pyScene.hh"
Expand Down Expand Up @@ -84,6 +87,10 @@ PYBIND11_MODULE(BINDINGS_MODULE_NAME, m) {
sdf::python::defineCollision(m);
sdf::python::defineContact(m);
sdf::python::defineCylinder(m);
// PrintConfig has to be defined before Param and Element because it's used as
// a default argument.
sdf::python::definePrintConfig(m);
sdf::python::defineElement(m);
sdf::python::defineEllipsoid(m);
sdf::python::defineError(m);
sdf::python::defineForceTorque(m);
Expand All @@ -107,6 +114,7 @@ PYBIND11_MODULE(BINDINGS_MODULE_NAME, m) {
sdf::python::defineNavSat(m);
sdf::python::defineNoise(m);
sdf::python::defineODE(m);
sdf::python::defineParam(m);
sdf::python::defineParserConfig(m);
sdf::python::defineParticleEmitter(m);
sdf::python::definePbr(m);
Expand Down
256 changes: 256 additions & 0 deletions python/src/sdf/pyElement.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
/*
* Copyright (C) 2023 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 "pyElement.hh"

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

#include <memory>
#include <string>

#include "pyParam.hh"
#include "pybind11_helpers.hh"
#include "sdf/Element.hh"
#include "sdf/Param.hh"
#include "sdf/PrintConfig.hh"
#include "sdf/config.hh"

using namespace pybind11::literals;
namespace py = pybind11;
namespace sdf
{
// Inline bracket to help doxygen filtering.
inline namespace SDF_VERSION_NAMESPACE {
namespace python
{

/////////////////////////////////////////////////
// The following functions have been excluded from the bindings because they're
// mainly used internally by the parser or there are better alternatives.
// - GetCopyChildren (Used by the parser for handling unknown elements)
// - SetCopyChildren (Used by the parser for handling unknown elements)
// - SetReferenceSDF (Used only by the parser)
// - ReferenceSDF (Used only by the parser)
// - PrintDescription (Use GetDescription() and print)
// - PrintValues (Because ToString is available)
// - PrintDocLeftPane (Helper function for generating documentation)
// - PrintDocRightPane (Helper function for generating documentation)
// - HasUniqueChildNames (Used for error checking by the parser)
// - CountNamedElements (Used for error checking by the parser)
// - GetElement (FindElement and AddElement should be used instead)
// - GetElementImpl (Use FindElement instead)
// - GetElementTypeNames (Used for error checking by the parser)
// - NameUniquenessExceptions (Used for error checking by the parser)
void defineElement(py::object module)
{
using PyClassElement = py::class_<Element, ElementPtr>;

auto elemClass = PyClassElement(module, "Element");
elemClass.def(py::init<>())
.def("clone", ErrorWrappedCast<>(&Element::Clone, py::const_),
"Create a copy of this Element.")
.def("copy", ErrorWrappedCast<ElementPtr>(&Element::Copy),
"Copy values from an Element.")
.def("get_parent", &Element::GetParent,
"Get a pointer to this Element's parent.")
.def("set_parent", &Element::SetParent, "Set the parent of this Element.")
.def("set_name", &Element::SetName, "Set the name of the Element")
.def("get_name", &Element::GetName, "Get the Element's name.")
.def("set_required", &Element::SetRequired, "Set the requirement type.")
.def("get_required", &Element::GetRequired, "Get the requirement string.")
.def("set_explicitly_set_in_file", &Element::SetExplicitlySetInFile,
"Set if the element and children where set or default in the "
"original file")
.def("get_explicitly_set_in_file", &Element::GetExplicitlySetInFile,
"Return if the element was been explicitly set in the file")
.def("to_string",
ErrorWrappedCast<const std::string &, const PrintConfig &>(
&Element::ToString, py::const_),
"Convert the element values to a string representation.", "prefix"_a,
"config"_a = PrintConfig())
.def(
"to_string",
ErrorWrappedCast<const std::string &, bool, bool,
const PrintConfig &>(&Element::ToString, py::const_),
"Convert the element values to a string representation.", "prefix"_a,
"include_default_elements"_a, "include_default_attribute"_a,
"config"_a = PrintConfig())
.def(
"add_attribute",
[](Element &_self, const std::string &_key, const std::string &_type,
const std::string &_defaultvalue, bool _required,
const std::string &_description = "")
{
Errors errors;
_self.AddAttribute(_key, _type, _defaultvalue, _required, errors,
_description);
ThrowIfErrors(errors);
},
"Add an attribute value.", "key"_a, "type"_a, "default_value"_a,
"required"_a, "description"_a = "")
.def(
"add_value",
[](Element &_self, const std::string &_type,
const std::string &_defaultValue, bool _required,
const std::string &_description = "")
{
Errors errors;
_self.AddValue(_type, _defaultValue, _required, errors,
_description);
ThrowIfErrors(errors);
},
"Add a value to this Element", "type"_a, "default_value"_a,
"required"_a, "description"_a = "")
.def(
"add_value",
[](Element &_self, const std::string &_type,
const std::string &_defaultValue, bool _required,
const std::string &_minValue, const std::string &_maxValue,
const std::string &_description = "")
{
Errors errors;
_self.AddValue(_type, _defaultValue, _required, _minValue,
_maxValue, errors, _description);
ThrowIfErrors(errors);
},
"Add a value to this Element", "type"_a, "default_value"_a,
"required"_a, "min_value"_a, "max_value"_a, "description"_a = "")
.def("get_attribute",
py::overload_cast<const std::string &>(&Element::GetAttribute,
py::const_),
"Get the param of an attribute.")
.def("get_attribute_count", &Element::GetAttributeCount,
"Get the number of attributes.")
.def("get_attributes", &Element::GetAttributes,
"Get all the attribute params.")
.def("get_attribute",
py::overload_cast<unsigned int>(&Element::GetAttribute, py::const_),
"Get the param of an attribute.")
.def("get_element_description_count",
&Element::GetElementDescriptionCount,
"Get the number of element descriptions.")
.def("get_element_description",
py::overload_cast<unsigned int>(&Element::GetElementDescription,
py::const_),
"Get an element description using an index")
.def("get_element_description",
py::overload_cast<const std::string &>(
&Element::GetElementDescription, py::const_),
"Get an element description using a key")
.def("has_element_description", &Element::HasElementDescription,
"Return true if an element description exists.")
.def("has_attribute", &Element::HasAttribute,
"Return true if an attribute exists.")
.def("get_attribute_set", &Element::GetAttributeSet,
"Return true if the attribute was set (i.e. not default value)")
.def("remove_attribute", &Element::RemoveAttribute,
"Remove an attribute.")
.def("remove_all_attributes", &Element::RemoveAllAttributes,
"Removes all attributes.")
.def("get_value", &Element::GetValue,
"Get the param of the elements value")
// get_any is not supported since std::any is not supported in
// pybind11.
// get and set are defined below
.def("has_element", &Element::HasElement,
"Return true if the named element exists.")
.def("get_first_element", &Element::GetFirstElement,
"Get the first child element")
.def("get_next_element", &Element::GetNextElement,
"Get the first child Get the next sibling of this element.")
.def("find_element",
py::overload_cast<const std::string &>(&Element::FindElement),
"Return a pointer to the child element with the provided name.")
.def("add_element",
ErrorWrappedCast<const std::string &>(&Element::AddElement),
"Add a value to this Element")
.def("insert_element",
py::overload_cast<ElementPtr>(&Element::InsertElement),
"Add an element object.")
.def("insert_element",
py::overload_cast<ElementPtr, bool>(&Element::InsertElement),
"Add an element object, and optionally set the given element's "
"parent to this object")
.def("remove_from_parent", &Element::RemoveFromParent,
"Remove this element from its parent.")
.def("remove_child", ErrorWrappedCast<ElementPtr>(&Element::RemoveChild),
"Remove a child element.")
.def("clear_elements", &Element::ClearElements,
"Remove all child elements.")
.def("clear", &Element::Clear,
"Remove all child elements and reset file path and original "
"version.")
.def("update", ErrorWrappedCast<>(&Element::Update),
"Call the Update() callback on each element, as well as the "
"embedded Param.")
.def("reset", &Element::Reset,
"Call reset on each element and element description before "
"deleting all of them. Also clear out the embedded Param.")
.def("set_include_element", &Element::SetIncludeElement,
"Set the `<include>` element that was used to load this element")
.def("get_include_element", &Element::GetIncludeElement,
"Get the `<include>` element that was used to load this element.")
.def("set_file_path", &Element::SetFilePath,
"Set the path to the SDF document where this element came from.")
.def("file_path", &Element::FilePath,
"Get the path to the SDF document where this element came from")
.def("set_line_number", &Element::SetLineNumber,
"Set the line number of this element within the SDF document.")
.def("line_number", &Element::LineNumber,
"Get the line number of this element within the SDF document.")
.def("set_xml_path", &Element::SetXmlPath,
"Set the XML path of this element.")
.def("xml_path", &Element::XmlPath, "Get the XML path of this element.")
.def("set_original_version", &Element::SetOriginalVersion,
"Set the spec version that this was originally parsed from.")
.def("original_version", &Element::OriginalVersion,
"Get the spec version that this was originally parsed from.")
.def("get_description", &Element::GetDescription,
"Get a text description of the element.")
.def("set_description", &Element::SetDescription,
"Set a text description for the element.")
.def("add_element_description", &Element::AddElementDescription,
"Add a new element description");

// Definitions for `Get<T>`, and `Set<T>` which will bind to `get_bool`,
// `get_int`, etc.
forEachParamType(
[&elemClass](auto &&arg)
{
using T = std::decay_t<decltype(arg)>;
const std::string getFuncName = "get_" + computeSuffix<T>();
const std::string setFuncName = "set_" + computeSuffix<T>();
elemClass
.def(getFuncName.c_str(),
ErrorWrappedCast<const std::string &>(&Element::Get<T>,
py::const_),
"Get the value of a key. This function assumes the _key "
"exists.",
"key"_a = "")
.def(getFuncName.c_str(),
ErrorWrappedCast<const std::string &, const T &>(
&Element::Get<T>, py::const_),
"Get the value of a key.")
.def(setFuncName.c_str(),
ErrorWrappedCast<const T &>(&Element::Set<T>),
"Get the value of a key. This function assumes the _key "
"exists.");
});
}
} // namespace python
} // namespace SDF_VERSION_NAMESPACE
} // namespace sdf
Loading

0 comments on commit c7560db

Please sign in to comment.