5ae6b5fe31
PiperOrigin-RevId: 859599919 Change-Id: If785f36010a7437b354ec0ce7db30c2b7ec69fe6
951 lines
34 KiB
Python
951 lines
34 KiB
Python
# Copyright 2024 DeepMind Technologies Limited
|
|
#
|
|
# 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.
|
|
# ==============================================================================
|
|
"""Generates the bindings for the MuJoCo specs."""
|
|
|
|
from collections.abc import Sequence
|
|
|
|
from absl import app
|
|
|
|
from introspect import ast_nodes
|
|
from introspect import structs
|
|
|
|
|
|
SCALAR_TYPES = {'int', 'double', 'float', 'mjtByte', 'mjtNum'}
|
|
|
|
# pylint: disable=bad-whitespace
|
|
# key, parent, default, listname, objtype
|
|
SPECS = [
|
|
('mjsBody', 'Body', True, 'bodies', 'mjOBJ_BODY'),
|
|
('mjsSite', 'Body', True, 'sites', 'mjOBJ_SITE'),
|
|
('mjsGeom', 'Body', True, 'geoms', 'mjOBJ_GEOM'),
|
|
('mjsJoint', 'Body', True, 'joints', 'mjOBJ_JOINT'),
|
|
('mjsCamera', 'Body', True, 'cameras', 'mjOBJ_CAMERA'),
|
|
('mjsFrame', 'Body', True, 'frames', 'mjOBJ_FRAME'),
|
|
('mjsLight', 'Body', True, 'lights', 'mjOBJ_LIGHT'),
|
|
('mjsFlex', 'Spec', False, 'flexes', 'mjOBJ_FLEX'),
|
|
('mjsMesh', 'Spec', True, 'meshes', 'mjOBJ_MESH'),
|
|
('mjsSkin', 'Spec', False, 'skins', 'mjOBJ_SKIN'),
|
|
('mjsHField', 'Spec', False, 'hfields', 'mjOBJ_HFIELD'),
|
|
('mjsTexture', 'Spec', False, 'textures', 'mjOBJ_TEXTURE'),
|
|
('mjsMaterial', 'Spec', True, 'materials', 'mjOBJ_MATERIAL'),
|
|
('mjsPair', 'Spec', True, 'pairs', 'mjOBJ_PAIR'),
|
|
('mjsEquality', 'Spec', True, 'equalities', 'mjOBJ_EQUALITY'),
|
|
('mjsTendon', 'Spec', True, 'tendons', 'mjOBJ_TENDON'),
|
|
('mjsActuator', 'Spec', True, 'actuators', 'mjOBJ_ACTUATOR'),
|
|
('mjsSensor', 'Spec', False, 'sensors', 'mjOBJ_SENSOR'),
|
|
('mjsNumeric', 'Spec', False, 'numerics', 'mjOBJ_NUMERIC'),
|
|
('mjsText', 'Spec', False, 'texts', 'mjOBJ_TEXT'),
|
|
('mjsTuple', 'Spec', False, 'tuples', 'mjOBJ_TUPLE'),
|
|
('mjsKey', 'Spec', False, 'keys', 'mjOBJ_KEY'),
|
|
('mjsExclude', 'Spec', False, 'excludes', 'mjOBJ_EXCLUDE'),
|
|
('mjsPlugin', 'Spec', False, 'plugins', 'mjOBJ_PLUGIN'),
|
|
]
|
|
SPECS_ADD = SPECS + [
|
|
('mjsBody', 'Frame', True, 'bodies', 'mjOBJ_BODY'),
|
|
('mjsSite', 'Frame', True, 'sites', 'mjOBJ_SITE'),
|
|
('mjsGeom', 'Frame', True, 'geoms', 'mjOBJ_GEOM'),
|
|
('mjsJoint', 'Frame', True, 'joints', 'mjOBJ_JOINT'),
|
|
('mjsCamera', 'Frame', True, 'cameras', 'mjOBJ_CAMERA'),
|
|
('mjsFrame', 'Frame', True, 'frames', 'mjOBJ_FRAME'),
|
|
('mjsLight', 'Frame', True, 'lights', 'mjOBJ_LIGHT'),
|
|
]
|
|
# pylint: enable=bad-whitespace
|
|
|
|
|
|
def _value_binding_code(
|
|
field: ast_nodes.ValueType, classname: str = '', varname: str = ''
|
|
) -> str:
|
|
"""Creates a string that defines Python bindings for a value type."""
|
|
fulltype = field.name
|
|
if field.name not in SCALAR_TYPES:
|
|
fulltype += '&'
|
|
fullvarname = varname
|
|
rawclassname = classname.replace('mjs', 'raw::Mjs')
|
|
if classname == 'mjSpec': # raw mjSpec has a wrapper
|
|
rawclassname = classname.replace('mjS', 'MjS')
|
|
fullvarname = 'ptr->' + varname
|
|
if field.name.startswith('mjs'): # all other mjs are raw structs
|
|
fulltype = field.name.replace('mjs', 'raw::Mjs')
|
|
if (
|
|
field.name == 'mjsPlugin'
|
|
or field.name == 'mjsOrientation'
|
|
or field.name == 'mjsCompiler'
|
|
):
|
|
fulltype = fulltype + '&' # plugin, orientation, compiler aren't pointers
|
|
else:
|
|
fulltype = fulltype + '*'
|
|
# non-mjs structs
|
|
rawclassname = rawclassname.replace('mjOption', 'raw::MjOption')
|
|
rawclassname = rawclassname.replace('mjVisual', 'raw::MjVisual')
|
|
rawclassname = rawclassname.replace('mjStatistic', 'raw::MjStatistic')
|
|
fulltype = fulltype.replace('mjOption', 'raw::MjOption')
|
|
fulltype = fulltype.replace('mjVisual', 'raw::MjVisual')
|
|
fulltype = fulltype.replace('mjStatistic', 'raw::MjStatistic')
|
|
element = '.element' if fullvarname == 'plugin' else ''
|
|
|
|
def_property_args = (
|
|
f'"{varname}"',
|
|
f"""[]({rawclassname}& self) -> {fulltype} {{
|
|
return self.{fullvarname};
|
|
}}""",
|
|
f"""[]({rawclassname}& self, {fulltype} {varname}) {{
|
|
self.{fullvarname}{element} = {varname}{element};
|
|
}}""",
|
|
)
|
|
|
|
if field.name not in SCALAR_TYPES:
|
|
def_property_args += ('py::return_value_policy::reference_internal',)
|
|
|
|
return f'{classname}.def_property({",".join(def_property_args)});'
|
|
|
|
|
|
def _struct_binding_code(
|
|
field: ast_nodes.AnonymousStructDecl, classname: str = '', varname: str = ''
|
|
) -> str:
|
|
"""Creates a string that declares Python bindings for an anonymous struct."""
|
|
code = ''
|
|
name = classname + varname.title()
|
|
# explicitly generate for nested fields with arrays
|
|
if any(
|
|
isinstance(f, ast_nodes.StructFieldDecl)
|
|
and isinstance(f.type, ast_nodes.ArrayType)
|
|
for f in field.fields
|
|
):
|
|
for subfield in field.fields:
|
|
code += _binding_code(subfield, name)
|
|
# generate for the struct itself
|
|
field = ast_nodes.ValueType(name=name)
|
|
code += _value_binding_code(field, classname, varname)
|
|
return code
|
|
|
|
|
|
def _array_binding_code(
|
|
field: ast_nodes.ArrayType, classname: str = '', varname: str = ''
|
|
) -> str:
|
|
"""Creates a string that declares Python bindings for an array type."""
|
|
if len(field.extents) > 1:
|
|
raise NotImplementedError()
|
|
innertype = field.inner_type.decl()
|
|
rawclassname = classname.replace('mjs', 'raw::Mjs')
|
|
rawclassname = rawclassname.replace('mjOption', 'raw::MjOption')
|
|
rawclassname = rawclassname.replace('mjVisual', 'raw::MjVisual')
|
|
rawclassname = rawclassname.replace('mjStatistic', 'raw::MjStatistic')
|
|
fullvarname = varname
|
|
if classname == 'mjSpec': # raw mjSpec has a wrapper
|
|
rawclassname = classname.replace('mjS', 'MjS')
|
|
fullvarname = 'ptr->' + varname
|
|
if innertype == 'double' or innertype == 'mjtNum':
|
|
innertype = 'MjDouble' # custom Eigen type
|
|
elif innertype == 'float':
|
|
innertype = 'MjFloat' # custom Eigen type
|
|
elif innertype == 'int':
|
|
innertype = 'MjInt' # custom Eigen type
|
|
elif innertype == 'char':
|
|
# char array special case
|
|
return f"""\
|
|
{classname}.def_property(
|
|
"{varname}",
|
|
[]({rawclassname}& self) -> MjTypeVec<char> {{
|
|
return MjTypeVec<char>(self.{fullvarname}, {field.extents[0]});
|
|
}},
|
|
[]({rawclassname}& self, py::object rhs) {{
|
|
int i = 0;
|
|
for (auto val : rhs) {{
|
|
self.{fullvarname}[i++] = py::cast<char>(val);
|
|
}}
|
|
}}, py::return_value_policy::move);"""
|
|
# all other array types
|
|
return f"""\
|
|
{classname}.def_property(
|
|
"{varname}",
|
|
[]({rawclassname}& self) -> {innertype}{field.extents[0]} {{
|
|
return {innertype}{field.extents[0]}(self.{fullvarname});
|
|
}},
|
|
[]({rawclassname}& self, {innertype}Ref{field.extents[0]} {varname}) {{
|
|
{innertype}{field.extents[0]}(self.{fullvarname}) = {varname};
|
|
}}, py::return_value_policy::reference_internal);"""
|
|
|
|
|
|
def _ptr_binding_code(
|
|
field: ast_nodes.PointerType, classname: str = '', varname: str = ''
|
|
) -> str:
|
|
"""Creates a string that declares Python bindings for a pointer type."""
|
|
vartype = field.inner_type.decl()
|
|
rawclassname = classname.replace('mjs', 'raw::Mjs')
|
|
fullvarname = varname
|
|
if classname == 'mjSpec': # raw mjSpec has a wrapper
|
|
rawclassname = classname.replace('mjS', 'MjS')
|
|
fullvarname = 'ptr->' + varname
|
|
if vartype == 'mjsElement': # this is ignored by the caller
|
|
return 'mjsElement'
|
|
if vartype.startswith('mjs'): # for structs, use the value case
|
|
return _value_binding_code(field.inner_type, classname, varname)
|
|
elif vartype == 'mjString': # C++ string -> Python string
|
|
return f"""\
|
|
{classname}.def_property(
|
|
"{varname}",
|
|
[]({rawclassname}& self) -> std::string_view {{
|
|
return *self.{fullvarname};
|
|
}},
|
|
[]({rawclassname}& self, std::string_view {varname}) {{
|
|
*(self.{fullvarname}) = {varname};
|
|
}});"""
|
|
elif ( # C++ vectors of values -> custom array
|
|
vartype == 'mjDoubleVec'
|
|
or vartype == 'mjFloatVec'
|
|
or vartype == 'mjIntVec'
|
|
):
|
|
vartype = vartype.replace('mj', '').replace('Vec', '').lower()
|
|
return f"""\
|
|
{classname}.def_property(
|
|
"{varname}",
|
|
[]({rawclassname}& self) -> MjTypeVec<{vartype}> {{
|
|
return MjTypeVec<{vartype}>(self.{fullvarname}->data(),
|
|
self.{fullvarname}->size());
|
|
}},
|
|
[]({rawclassname}& self, py::object rhs) {{
|
|
self.{fullvarname}->clear();
|
|
self.{fullvarname}->reserve(py::len(rhs));
|
|
for (auto val : rhs) {{
|
|
self.{fullvarname}->push_back(py::cast<{vartype}>(val));
|
|
}}
|
|
}}, py::return_value_policy::move);"""
|
|
elif vartype == 'mjByteVec':
|
|
return f"""\
|
|
{classname}.def_property(
|
|
"{varname}",
|
|
[]({rawclassname}& self) -> py::bytes {{
|
|
return py::bytes(reinterpret_cast<const char*>(self.{fullvarname}->data()),
|
|
self.{fullvarname}->size());
|
|
}},
|
|
[]({rawclassname}& self, py::bytes rhs) {{
|
|
self.{fullvarname}->clear();
|
|
std::string_view rhs_view = py::cast<std::string_view>(rhs);
|
|
self.{fullvarname}->reserve(rhs_view.length());
|
|
for (char val : rhs_view) {{
|
|
self.{fullvarname}->push_back(static_cast<std::byte>(val));
|
|
}}
|
|
}}, py::return_value_policy::move);"""
|
|
elif vartype == 'mjStringVec':
|
|
# Special case for material.textures: must be exactly mjNTEXROLE size
|
|
if classname == 'mjsMaterial' and varname == 'textures':
|
|
return f"""\
|
|
{classname}.def_property(
|
|
"{varname}",
|
|
[]({rawclassname}& self) -> MjTypeVec<std::string> {{
|
|
return MjTypeVec<std::string>(self.{fullvarname}->data(),
|
|
self.{fullvarname}->size());
|
|
}},
|
|
[]({rawclassname}& self, py::object rhs) {{
|
|
if (py::len(rhs) != mjNTEXROLE) {{
|
|
throw pybind11::value_error(
|
|
"material.textures must have exactly " + std::to_string(mjNTEXROLE) +
|
|
" elements, got " + std::to_string(py::len(rhs)) + ". " +
|
|
"Assign a list of " + std::to_string(mjNTEXROLE) + " texture names " +
|
|
"(use empty strings '' for unused slots).");
|
|
}}
|
|
self.{fullvarname}->clear();
|
|
self.{fullvarname}->reserve(mjNTEXROLE);
|
|
for (auto val : rhs) {{
|
|
self.{fullvarname}->push_back(py::cast<std::string>(val));
|
|
}}
|
|
}}, py::return_value_policy::move);"""
|
|
# Default case for other mjStringVec properties
|
|
return f"""\
|
|
{classname}.def_property(
|
|
"{varname}",
|
|
[]({rawclassname}& self) -> MjTypeVec<std::string> {{
|
|
return MjTypeVec<std::string>(self.{fullvarname}->data(),
|
|
self.{fullvarname}->size());
|
|
}},
|
|
[]({rawclassname}& self, py::object rhs) {{
|
|
self.{fullvarname}->clear();
|
|
self.{fullvarname}->reserve(py::len(rhs));
|
|
for (auto val : rhs) {{
|
|
self.{fullvarname}->push_back(py::cast<std::string>(val));
|
|
}}
|
|
}}, py::return_value_policy::move);"""
|
|
elif 'VecVec' in vartype: # C++ vector of vectors -> Python list of lists
|
|
vartype = vartype.replace('mj', '').replace('VecVec', '').lower()
|
|
return f"""\
|
|
{classname}.def_property(
|
|
"{varname}",
|
|
[]({rawclassname}& self) -> py::list {{
|
|
py::list list;
|
|
for (auto inner_vec : *self.{fullvarname}) {{
|
|
py::list inner_list;
|
|
for (auto val : inner_vec) {{
|
|
inner_list.append(val);
|
|
}}
|
|
list.append(inner_list);
|
|
}}
|
|
return list;
|
|
}},
|
|
[]({rawclassname}& self, py::object rhs) {{
|
|
self.{fullvarname}->clear();
|
|
self.{fullvarname}->reserve(py::len(rhs));
|
|
for (auto inner_list : rhs) {{
|
|
auto inner_vec = py::cast<std::vector<{vartype}>>(inner_list);
|
|
self.{fullvarname}->push_back(inner_vec);
|
|
}}
|
|
}}, py::return_value_policy::reference_internal);"""
|
|
|
|
raise NotImplementedError(
|
|
'Unsupported array type: ' + vartype + ' in ' + classname
|
|
)
|
|
|
|
|
|
def _binding_code(field: ast_nodes.StructFieldDecl, key: str) -> str:
|
|
if isinstance(field.type, ast_nodes.ValueType):
|
|
return _value_binding_code(field.type, key, field.name)
|
|
elif isinstance(field.type, ast_nodes.AnonymousStructDecl):
|
|
return _struct_binding_code(field.type, key, field.name)
|
|
elif isinstance(field.type, ast_nodes.PointerType):
|
|
return _ptr_binding_code(field.type, key, field.name)
|
|
elif isinstance(field.type, ast_nodes.ArrayType):
|
|
return _array_binding_code(field.type, key, field.name)
|
|
return ''
|
|
|
|
|
|
def generate() -> None:
|
|
for key in structs.STRUCTS.keys():
|
|
if (
|
|
key.startswith('mjs')
|
|
or key in ['mjSpec', 'mjOption', 'mjVisual', 'mjStatistic']
|
|
) and key != 'mjsElement':
|
|
print('\n // ' + key)
|
|
for field in structs.STRUCTS[key].fields:
|
|
code = _binding_code(field, key)
|
|
if code != 'mjsElement':
|
|
print(code)
|
|
|
|
|
|
def generate_add() -> None:
|
|
"""Generate add constructors with optional keyword arguments."""
|
|
for key, parent, default, listname, objtype in SPECS_ADD:
|
|
|
|
def _field(f: ast_nodes.StructFieldDecl):
|
|
if f.type == ast_nodes.PointerType(
|
|
inner_type=ast_nodes.ValueType(name='mjsElement')
|
|
):
|
|
return '', '', '', '', '', ''
|
|
elif f.type == ast_nodes.ValueType(name='mjsPlugin'):
|
|
return (
|
|
f'set_plugin(out->{f.name}, plugin);',
|
|
'plugin',
|
|
f.name,
|
|
'MjsPlugin',
|
|
'std::optional<raw::MjsPlugin>& plugin',
|
|
'py::arg("plugin") = py::none()',
|
|
)
|
|
elif f.type == ast_nodes.ValueType(name='mjsOrientation'):
|
|
return (
|
|
(
|
|
f'set_orientation(out->{f.name},'
|
|
f' {"iaxisangle" if f.name == "ialt" else "axisangle"},'
|
|
f' {"ixyaxes" if f.name == "ialt" else "xyaxes"},'
|
|
f' {"izaxis" if f.name == "ialt" else "zaxis"},'
|
|
f' {"ieuler" if f.name == "ialt" else "euler"},'
|
|
f' "{"iaxisangle" if f.name == "ialt" else "axisangle"}",'
|
|
f' "{"ixyaxes" if f.name == "ialt" else "xyaxes"}",'
|
|
f' "{"izaxis" if f.name == "ialt" else "zaxis"}",'
|
|
f' "{"ieuler" if f.name == "ialt" else "euler"}");'
|
|
),
|
|
'orientation',
|
|
['iaxisangle', 'ixyaxes', 'izaxis', 'ieuler']
|
|
if f.name == 'ialt'
|
|
else ['axisangle', 'xyaxes', 'zaxis', 'euler'],
|
|
'list[float]',
|
|
[
|
|
f'std::optional<std::vector<double>>& {n}'
|
|
for n in (
|
|
['iaxisangle', 'ixyaxes', 'izaxis', 'ieuler']
|
|
if f.name == 'ialt'
|
|
else ['axisangle', 'xyaxes', 'zaxis', 'euler']
|
|
)
|
|
],
|
|
[
|
|
f'py::arg("{n}") = py::none()'
|
|
for n in (
|
|
['iaxisangle', 'ixyaxes', 'izaxis', 'ieuler']
|
|
if f.name == 'ialt'
|
|
else ['axisangle', 'xyaxes', 'zaxis', 'euler']
|
|
)
|
|
],
|
|
)
|
|
elif f.type == ast_nodes.PointerType(
|
|
inner_type=ast_nodes.ValueType(name='mjString')
|
|
):
|
|
return (
|
|
f'set_string(out->{f.name}, {f.name});',
|
|
'string',
|
|
f.name,
|
|
'str',
|
|
f'std::optional<std::string>& {f.name}',
|
|
f'py::arg("{f.name}") = py::none()',
|
|
)
|
|
elif f.type == ast_nodes.PointerType(
|
|
inner_type=ast_nodes.ValueType(name='mjStringVec')
|
|
):
|
|
return (
|
|
f'set_str_vec(out->{f.name}, {f.name});',
|
|
'str_vec',
|
|
f.name,
|
|
'list[str]',
|
|
f'std::optional<std::vector<std::string>>& {f.name}',
|
|
f'py::arg("{f.name}") = py::none()',
|
|
)
|
|
|
|
# Handle other vector types
|
|
inner_name = (
|
|
f.type.inner_type.name
|
|
if isinstance(f.type, ast_nodes.PointerType)
|
|
and isinstance(f.type.inner_type, ast_nodes.ValueType)
|
|
else ''
|
|
)
|
|
|
|
if inner_name in ('mjIntVec', 'mjByteVec'):
|
|
return (
|
|
f'set_int_vec(out->{f.name}, {f.name});',
|
|
'int_vec',
|
|
f.name,
|
|
'list[int]',
|
|
f'std::optional<std::vector<int>>& {f.name}',
|
|
f'py::arg("{f.name}") = py::none()',
|
|
)
|
|
elif inner_name in ('mjFloatVec', 'mjDoubleVec'):
|
|
return (
|
|
f'set_vec(out->{f.name}, {f.name});',
|
|
'vec',
|
|
f.name,
|
|
'list[float]',
|
|
f'std::optional<std::vector<double>>& {f.name}',
|
|
f'py::arg("{f.name}") = py::none()',
|
|
)
|
|
elif inner_name == 'mjIntVecVec':
|
|
return (
|
|
f'set_int_vec_vec(out->{f.name}, {f.name});',
|
|
'int_vec_vec',
|
|
f.name,
|
|
'list[list[int]]',
|
|
f'std::optional<std::vector<std::vector<int>>>& {f.name}',
|
|
f'py::arg("{f.name}") = py::none()',
|
|
)
|
|
elif inner_name == 'mjFloatVecVec':
|
|
return (
|
|
f'set_float_vec_vec(out->{f.name}, {f.name});',
|
|
'float_vec_vec',
|
|
f.name,
|
|
'list[list[float]]',
|
|
f'std::optional<std::vector<std::vector<double>>>& {f.name}',
|
|
f'py::arg("{f.name}") = py::none()',
|
|
)
|
|
|
|
elif isinstance(f.type, ast_nodes.PointerType):
|
|
return (
|
|
f'set_vec(out->{f.name}, {f.name});',
|
|
'vec',
|
|
f.name,
|
|
'list[float]',
|
|
f'std::optional<std::vector<double>>& {f.name}',
|
|
f'py::arg("{f.name}") = py::none()',
|
|
)
|
|
elif isinstance(f.type, ast_nodes.ArrayType):
|
|
inner_type = f.type.inner_type.decl()
|
|
if inner_type == 'char':
|
|
return (
|
|
(
|
|
f'set_char_array(out->{f.name}, {f.name},'
|
|
f' {f.type.extents[0]}, "{f.name}");'
|
|
),
|
|
'char_array',
|
|
f.name,
|
|
'str | list[str]',
|
|
f'py::object& {f.name}',
|
|
f'py::arg("{f.name}") = py::none()',
|
|
)
|
|
if f.name == 'size' and f.type.extents[0] == 3:
|
|
return (
|
|
f'set_array_size(out->{f.name}, {f.name});',
|
|
'array_size',
|
|
f.name,
|
|
'list[float]',
|
|
f'std::optional<std::vector<double>>& {f.name}',
|
|
f'py::arg("{f.name}") = py::none()',
|
|
)
|
|
return (
|
|
(
|
|
f'set_array(out->{f.name}, {f.name}, {f.type.extents[0]},'
|
|
f' "{f.name}");'
|
|
),
|
|
'array',
|
|
f.name,
|
|
'list[float]',
|
|
f'std::optional<std::vector<double>>& {f.name}',
|
|
f'py::arg("{f.name}") = py::none()',
|
|
)
|
|
elif isinstance(f.type, ast_nodes.ValueType):
|
|
type_name = 'float'
|
|
cpp_type = 'double'
|
|
if f.type.name in ('int', 'mjtByte') or f.type.name.startswith('mjt'):
|
|
type_name = 'int'
|
|
cpp_type = 'int'
|
|
return (
|
|
f'set_value(out->{f.name}, {f.name});',
|
|
'value',
|
|
f.name,
|
|
type_name,
|
|
f'std::optional<{cpp_type}>& {f.name}',
|
|
f'py::arg("{f.name}") = py::none()',
|
|
)
|
|
else:
|
|
return '', '', '', '', '', ''
|
|
|
|
if key == 'mjsPlugin':
|
|
code_field = ''
|
|
set_types = []
|
|
names = []
|
|
types = []
|
|
cpp_args = []
|
|
py_args = []
|
|
else:
|
|
code_field = 'set_name(out->element, name);'
|
|
set_types = ['name']
|
|
names = ['name']
|
|
types = ['str']
|
|
cpp_args = ['std::optional<std::string>& name']
|
|
py_args = ['py::arg("name") = py::none()']
|
|
|
|
for field in structs.STRUCTS[key].fields:
|
|
line, set_type, name, type_name, cpp_arg, py_arg = _field(field)
|
|
if line:
|
|
code_field = code_field + '\n ' + line
|
|
set_types.append(set_type)
|
|
if set_type == 'orientation':
|
|
names.extend(name)
|
|
types.extend([type_name] * len(name))
|
|
cpp_args.extend(cpp_arg)
|
|
py_args.extend(py_arg)
|
|
else:
|
|
names.append(name)
|
|
types.append(type_name)
|
|
cpp_args.append(cpp_arg)
|
|
py_args.append(py_arg)
|
|
|
|
# assemble
|
|
elem = key.removeprefix('mjs')
|
|
elemlower = elem.lower()
|
|
titlecase = 'Mjs' + elem
|
|
|
|
docstring = f'Add {elemlower} to spec.\n\n Args:\n'
|
|
for i, name in enumerate(names):
|
|
if i > 0:
|
|
docstring += '\n'
|
|
docstring += f' {name}: {types[i]}'
|
|
|
|
# functions arguments
|
|
args = ', '.join(cpp_args)
|
|
if args:
|
|
args = ', ' + args
|
|
|
|
# py::arg definitions
|
|
pyargs = ', '.join(py_args)
|
|
|
|
# function definition and call to mjs_add_
|
|
if parent == 'Spec':
|
|
if default:
|
|
code = f"""
|
|
{'mj' + parent}.def("add_{elemlower}", []({'Mj' + parent}& self,
|
|
raw::MjsDefault* default_{args}) -> raw::{titlecase}* {{
|
|
auto out = mjs_add{elem}(self.ptr, default_);
|
|
"""
|
|
else:
|
|
code = f"""
|
|
{'mj' + parent}.def("add_{elemlower}", []({'Mj' + parent}& self{args}) -> raw::{titlecase}* {{
|
|
auto out = mjs_add{elem}(self.ptr);
|
|
"""
|
|
elif parent == 'Body':
|
|
if key == 'mjsFrame':
|
|
code = f"""
|
|
{'mjs' + parent}.def("add_{elemlower}", []({'raw::Mjs' + parent}& self,
|
|
raw::MjsFrame* parentframe_{args}) -> raw::{titlecase}* {{
|
|
auto out = mjs_add{elem}(&self, parentframe_);
|
|
"""
|
|
else:
|
|
code = f"""
|
|
{'mjs' + parent}.def("add_{elemlower}", []({'raw::Mjs' + parent}& self,
|
|
raw::MjsDefault* default_{args}) -> raw::{titlecase}* {{
|
|
auto out = mjs_add{elem}(&self, default_);
|
|
"""
|
|
elif parent == 'Frame':
|
|
if key == 'mjsFrame':
|
|
code = f"""
|
|
{'mjs' + parent}.def("add_{elemlower}", []({'raw::Mjs' + parent}& self,
|
|
raw::MjsFrame* parentframe_{args}) -> raw::{titlecase}* {{
|
|
raw::MjsBody* body = mjs_getParent(self.element);
|
|
auto out = mjs_add{elem}(body, &self);
|
|
"""
|
|
else:
|
|
code = f"""
|
|
{'mjs' + parent}.def("add_{elemlower}", []({'raw::Mjs' + parent}& self,
|
|
raw::MjsDefault* default_{args}) -> raw::{titlecase}* {{
|
|
raw::MjsBody* body = mjs_getParent(self.element);
|
|
auto out = mjs_add{elem}(body, default_);
|
|
mjs_setFrame(out->element, &self);
|
|
"""
|
|
else:
|
|
raise NotImplementedError(f'{parent} parent is not implement.')
|
|
|
|
# include helper functions
|
|
if set_types:
|
|
for t in set(set_types):
|
|
if t == 'orientation':
|
|
code += """\n
|
|
auto set_orientation = [](raw::MjsOrientation& orientation,
|
|
const std::optional<std::vector<double>>& axisangle,
|
|
const std::optional<std::vector<double>>& xyaxes,
|
|
const std::optional<std::vector<double>>& zaxis,
|
|
const std::optional<std::vector<double>>& euler,
|
|
const char* name_axisangle,
|
|
const char* name_xyaxes,
|
|
const char* name_zaxis,
|
|
const char* name_euler) {
|
|
int nrepresentation = 0;
|
|
nrepresentation += axisangle.has_value();
|
|
nrepresentation += xyaxes.has_value();
|
|
nrepresentation += zaxis.has_value();
|
|
nrepresentation += euler.has_value();
|
|
|
|
if (nrepresentation == 0) {
|
|
return;
|
|
} else if (nrepresentation > 1) {
|
|
std::string msg = std::string("Only one of: ") + name_axisangle + ", " + name_xyaxes + ", " + name_zaxis + ", or " + name_euler + " can be set.";
|
|
throw pybind11::value_error(msg);
|
|
}
|
|
|
|
auto set_array = [](const std::vector<double>& array, double* des, int size, const char* name) {
|
|
if (array.size() != size) {
|
|
std::string msg = std::string(name) + " should be a list/array of size " + std::to_string(size) + ".";
|
|
throw pybind11::value_error(msg);
|
|
}
|
|
int idx = 0;
|
|
for (auto val : array) {
|
|
des[idx++] = val;
|
|
}
|
|
};
|
|
if (axisangle.has_value()) {
|
|
set_array(axisangle.value(), orientation.axisangle, 4, name_axisangle);
|
|
orientation.type = mjORIENTATION_AXISANGLE;
|
|
} else if (xyaxes.has_value()) {
|
|
set_array(xyaxes.value(), orientation.xyaxes, 6, name_xyaxes);
|
|
orientation.type = mjORIENTATION_XYAXES;
|
|
} else if (zaxis.has_value()) {
|
|
set_array(zaxis.value(), orientation.zaxis, 3, name_zaxis);
|
|
orientation.type = mjORIENTATION_ZAXIS;
|
|
} else if (euler.has_value()) {
|
|
set_array(euler.value(), orientation.euler, 3, name_euler);
|
|
orientation.type = mjORIENTATION_EULER;
|
|
}
|
|
};
|
|
"""
|
|
elif t == 'plugin':
|
|
code += """\n
|
|
auto set_plugin = [](raw::MjsPlugin& plugin, const std::optional<raw::MjsPlugin>& input) {
|
|
if (input.has_value()) {
|
|
plugin.name = input->name;
|
|
plugin.plugin_name = input->plugin_name;
|
|
plugin.active = input->active;
|
|
plugin.info = input->info;
|
|
}
|
|
};
|
|
"""
|
|
elif t == 'string':
|
|
code += """\n
|
|
auto set_string = [](std::basic_string<char>* des, const std::optional<std::string>& str) {
|
|
if (str.has_value()) {
|
|
*des = str.value();
|
|
}
|
|
};
|
|
"""
|
|
elif t == 'str_vec':
|
|
code += """\n
|
|
auto set_str_vec = [](auto&& des, const std::optional<std::vector<std::string>>& vec) {
|
|
if (vec.has_value()) {
|
|
des->clear();
|
|
des->reserve(vec->size());
|
|
for (const auto& val : vec.value()) {
|
|
des->push_back(val);
|
|
}
|
|
}
|
|
};
|
|
"""
|
|
|
|
elif t == 'int_vec':
|
|
code += """\n
|
|
auto set_int_vec = [](auto&& des, const std::optional<std::vector<int>>& vec) {
|
|
if (vec.has_value()) {
|
|
using T = typename std::decay_t<decltype(*des)>::value_type;
|
|
des->clear();
|
|
des->reserve(vec->size());
|
|
for (auto val : vec.value()) {
|
|
des->push_back(static_cast<T>(val));
|
|
}
|
|
}
|
|
};
|
|
"""
|
|
elif t == 'int_vec_vec':
|
|
code += """\n
|
|
auto set_int_vec_vec = [](auto&& des, const std::optional<std::vector<std::vector<int>>>& vec) {
|
|
if (vec.has_value()) {
|
|
des->clear();
|
|
des->reserve(vec->size());
|
|
for (const auto& inner : vec.value()) {
|
|
using InnerT = typename std::decay_t<decltype(*des)>::value_type;
|
|
InnerT inner_res;
|
|
inner_res.reserve(inner.size());
|
|
using ValT = typename InnerT::value_type;
|
|
for (auto val : inner) {
|
|
inner_res.push_back(static_cast<ValT>(val));
|
|
}
|
|
des->push_back(inner_res);
|
|
}
|
|
}
|
|
};
|
|
"""
|
|
elif t == 'float_vec_vec':
|
|
code += """\n
|
|
auto set_float_vec_vec = [](auto&& des, const std::optional<std::vector<std::vector<double>>>& vec) {
|
|
if (vec.has_value()) {
|
|
des->clear();
|
|
des->reserve(vec->size());
|
|
for (const auto& inner : vec.value()) {
|
|
using InnerT = typename std::decay_t<decltype(*des)>::value_type;
|
|
InnerT inner_res;
|
|
inner_res.reserve(inner.size());
|
|
using ValT = typename InnerT::value_type;
|
|
for (auto val : inner) {
|
|
inner_res.push_back(static_cast<ValT>(val));
|
|
}
|
|
des->push_back(inner_res);
|
|
}
|
|
}
|
|
};
|
|
"""
|
|
|
|
elif t == 'vec':
|
|
code += """\n
|
|
auto set_vec = [](auto&& des, const std::optional<std::vector<double>>& vec) {
|
|
if (vec.has_value()) {
|
|
using T = typename std::decay_t<decltype(*des)>::value_type;
|
|
des->clear();
|
|
des->reserve(vec->size());
|
|
for (auto val : vec.value()) {
|
|
des->push_back(static_cast<T>(val));
|
|
}
|
|
}
|
|
};
|
|
"""
|
|
elif t == 'array':
|
|
code += """\n
|
|
auto set_array = [](auto&& des, const std::optional<std::vector<double>>& array, int size, const char* name) {
|
|
if (array.has_value()) {
|
|
if (array->size() != size) {
|
|
std::string msg = std::string(name) + " should be a list/array of size " + std::to_string(size) + ".";
|
|
throw pybind11::value_error(msg);
|
|
}
|
|
int idx = 0;
|
|
for (auto val : array.value()) {
|
|
des[idx++] = val;
|
|
}
|
|
}
|
|
};
|
|
"""
|
|
elif t == 'char_array':
|
|
code += """\n
|
|
auto set_char_array = [](auto&& des, py::object& obj, int size, const char* name) {
|
|
if (obj.is_none()) {
|
|
return;
|
|
}
|
|
std::string chars;
|
|
if (py::isinstance<py::str>(obj)) {
|
|
chars = py::cast<std::string>(obj);
|
|
} else if (py::isinstance<py::list>(obj)) {
|
|
py::list list = py::cast<py::list>(obj);
|
|
chars.reserve(py::len(list));
|
|
for (auto item : list) {
|
|
std::string s = py::cast<std::string>(item);
|
|
if (s.size() != 1) {
|
|
throw pybind11::value_error(std::string(name) + " list elements must be single characters.");
|
|
}
|
|
chars.push_back(s[0]);
|
|
}
|
|
} else {
|
|
throw pybind11::type_error(std::string(name) + " must be a string or a list of single-character strings.");
|
|
}
|
|
if (chars.size() != size) {
|
|
std::string msg = std::string(name) + " should have length " + std::to_string(size) + ", got " + std::to_string(chars.size()) + ".";
|
|
throw pybind11::value_error(msg);
|
|
}
|
|
int idx = 0;
|
|
for (char val : chars) {
|
|
des[idx++] = val;
|
|
}
|
|
};
|
|
"""
|
|
elif t == 'array_size':
|
|
code += """\n
|
|
auto set_array_size = [](auto&& des, const std::optional<std::vector<double>>& array) {
|
|
if (array.has_value()) {
|
|
if (array->size() < 1 || array->size() > 3) {
|
|
std::string msg = "size should be a list/array of size 1, 2, or 3.";
|
|
throw pybind11::value_error(msg);
|
|
}
|
|
for (int i = 0; i < 3; i++) {
|
|
des[i] = (i < array->size()) ? array->at(i) : 0;
|
|
}
|
|
}
|
|
};
|
|
"""
|
|
elif t == 'value':
|
|
code += """\n
|
|
auto set_value = [](auto&& des, auto&& val) {
|
|
if (val.has_value()) {
|
|
using T = std::decay_t<decltype(des)>;
|
|
des = static_cast<T>(val.value());
|
|
}
|
|
};
|
|
"""
|
|
elif t == 'name':
|
|
code += """\n
|
|
auto set_name = [](raw::MjsElement* el, const std::optional<std::string>& name) {
|
|
if (name.has_value()) {
|
|
mjs_setName(el, name->c_str());
|
|
}
|
|
};
|
|
"""
|
|
else:
|
|
raise NotImplementedError(f'Unsupported set type: {t} in {key}')
|
|
|
|
code += code_field
|
|
code += f"""\n
|
|
return out;
|
|
}},
|
|
{'py::arg_v("default", nullptr)' + (', ' if pyargs else '') if default else ''}
|
|
{pyargs}{', ' if pyargs else ''}
|
|
R"mydelimiter(
|
|
{docstring}
|
|
)mydelimiter",
|
|
py::return_value_policy::reference_internal);
|
|
"""
|
|
|
|
code += f"""\n
|
|
mjSpec.def_property_readonly(
|
|
"{listname}",
|
|
[](MjSpec& self) -> py::list {{
|
|
py::list list;
|
|
raw::MjsElement* el = mjs_firstElement(self.ptr, {objtype});
|
|
while (el) {{
|
|
list.append(mjs_as{key[3:]}(el));
|
|
el = mjs_nextElement(self.ptr, el);
|
|
}}
|
|
return list;
|
|
}},
|
|
py::return_value_policy::reference_internal);
|
|
"""
|
|
|
|
print(code)
|
|
|
|
|
|
def generate_find() -> None:
|
|
"""Generate find functions."""
|
|
for key, _, _, _, objtype in SPECS:
|
|
elem = key.removeprefix('mjs')
|
|
elemlower = elem.lower()
|
|
titlecase = 'Mjs' + elem
|
|
code = f"""\n
|
|
mjSpec.def("{elemlower}",
|
|
[](MjSpec& self, std::string& name) -> raw::{titlecase}* {{
|
|
return mjs_as{elem}(
|
|
mjs_findElement(self.ptr, {objtype}, name.c_str()));
|
|
}}, py::return_value_policy::reference_internal);
|
|
"""
|
|
print(code)
|
|
|
|
|
|
def generate_signature() -> None:
|
|
"""Generate signature functions."""
|
|
for key, _, _, _, _ in SPECS:
|
|
elem = key.removeprefix('mjs')
|
|
titlecase = 'Mjs' + elem
|
|
code = f"""\n
|
|
{key}.def_property_readonly("signature",
|
|
[](raw::{titlecase}& self) -> uint64_t {{
|
|
return mjs_getSpec(self.element)->element->signature;
|
|
}});
|
|
"""
|
|
print(code)
|
|
|
|
|
|
def generate_id() -> None:
|
|
"""Generate id functions."""
|
|
for key, _, _, _, _ in SPECS:
|
|
if key == 'mjsPlugin':
|
|
continue
|
|
elem = key.removeprefix('mjs')
|
|
titlecase = 'Mjs' + elem
|
|
code = f"""\n
|
|
{key}.def_property_readonly("id",
|
|
[](raw::{titlecase}& self) -> int {{
|
|
return mjs_getId(self.element);
|
|
}});
|
|
"""
|
|
print(code)
|
|
|
|
|
|
def generate_name() -> None:
|
|
"""Generate name functions."""
|
|
for key, _, _, _, _ in SPECS + [('mjsDefault', '', '', '', '')]:
|
|
if key == 'mjsPlugin':
|
|
continue
|
|
elem = key.removeprefix('mjs')
|
|
titlecase = 'Mjs' + elem
|
|
code = f"""\n
|
|
{key}.def_property("name",
|
|
[](raw::{titlecase}& self) -> std::string* {{
|
|
return mjs_getName(self.element);
|
|
}},
|
|
[](raw::{titlecase}& self, std::string& name) -> void {{
|
|
if (mjs_setName(self.element, name.c_str())) {{
|
|
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
|
|
}}
|
|
}}, py::return_value_policy::reference_internal);
|
|
"""
|
|
print(code)
|
|
|
|
|
|
def main(argv: Sequence[str]) -> None:
|
|
if len(argv) > 1:
|
|
raise app.UsageError('Too many command-line arguments.')
|
|
generate()
|
|
generate_add()
|
|
generate_find()
|
|
generate_signature()
|
|
generate_id()
|
|
generate_name()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
app.run(main)
|