Add mjs_makeFlex to the MuJoCo C and Python APIs.
- Add mjs_makeFlex C API that wraps mjCFlexcomp::Make(), creating a flex with auto-generated bodies, joints, and equality constraints from parameters (type, dim, dof, count, cellcount, spacing, scale, radius, mass, inertiabox, equality, rigid, flatskin, elastic2d, pos, quat, origin, file, vfs). - Add Python binding body.make_flex() in specs.cc with keyword args. - Refactor flex_spec_provision.py to use make_flex() instead of XML string templating + separate spec + attach. Eliminates textwrap dependency. - Merge user_flexcomp into user_model BUILD target to resolve circular dependency. PiperOrigin-RevId: 922725304 Change-Id: Ice13787c45f3173407fec659f9590a6453bc65dd
This commit is contained in:
committed by
Copybara-Service
parent
b9fb817af8
commit
4c381635e1
@@ -4496,6 +4496,17 @@ Add sensor.
|
||||
|
||||
Add flex.
|
||||
|
||||
.. _mjs_makeFlex:
|
||||
|
||||
`mjs_makeFlex <#mjs_makeFlex>`__
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. mujoco-include:: mjs_makeFlex
|
||||
|
||||
Add flexcomp: create flex with auto-generated bodies/joints, return flex spec.
|
||||
|
||||
*Nullable:* ``type``, ``dof``, ``count``, ``cellcount``, ``spacing``, ``scale``, ``pos``, ``quat``, ``origin``, ``file``, ``vfs``
|
||||
|
||||
.. _mjs_addPair:
|
||||
|
||||
`mjs_addPair <#mjs_addPair>`__
|
||||
|
||||
+6
-1
@@ -5,11 +5,16 @@ Changelog
|
||||
Upcoming version (not yet released)
|
||||
-----------------------------------
|
||||
|
||||
General
|
||||
^^^^^^^
|
||||
- Added :ref:`mjs_makeFlex`, a new C API function equivalent to the :ref:`flexcomp<body-flexcomp>` element for
|
||||
programmatically creating flex objects with auto-generated bodies, joints, and equality constraints. Exposed as
|
||||
``body.make_flex()`` in Python.
|
||||
|
||||
Bug fixes
|
||||
^^^^^^^^^
|
||||
- Fixed a bug in the ``mjz`` :ref:`decoder <mjpDecoder>` where unnormalized paths would fail to be read.
|
||||
|
||||
|
||||
Version 3.9.0 (May 27, 2026)
|
||||
----------------------------
|
||||
|
||||
|
||||
@@ -3678,6 +3678,12 @@ int mjs_delete(mjSpec* spec, mjsElement* element);
|
||||
mjsActuator* mjs_addActuator(mjSpec* s, const mjsDefault* def);
|
||||
mjsSensor* mjs_addSensor(mjSpec* s);
|
||||
mjsFlex* mjs_addFlex(mjSpec* s);
|
||||
mjsFlex* mjs_makeFlex(mjsBody* body, const char* name, const char* type, int dim,
|
||||
const char* dof, const int count[3], const int cellcount[3],
|
||||
const double spacing[3], const double scale[3], double radius,
|
||||
double mass, double inertiabox, int equality, int rigid, int flatskin,
|
||||
int elastic2d, const double pos[3], const double quat[4],
|
||||
const double origin[3], const char* file, const mjVFS* vfs);
|
||||
mjsPair* mjs_addPair(mjSpec* s, const mjsDefault* def);
|
||||
mjsExclude* mjs_addExclude(mjSpec* s);
|
||||
mjsEquality* mjs_addEquality(mjSpec* s, const mjsDefault* def);
|
||||
|
||||
@@ -1667,6 +1667,15 @@ MJAPI mjsSensor* mjs_addSensor(mjSpec* s);
|
||||
// Add flex.
|
||||
MJAPI mjsFlex* mjs_addFlex(mjSpec* s);
|
||||
|
||||
// Add flexcomp: create flex with auto-generated bodies/joints, return flex spec.
|
||||
// Nullable: type, dof, count, cellcount, spacing, scale, pos, quat, origin, file, vfs
|
||||
MJAPI mjsFlex* mjs_makeFlex(mjsBody* body, const char* name, const char* type, int dim,
|
||||
const char* dof, const int count[3], const int cellcount[3],
|
||||
const double spacing[3], const double scale[3], double radius,
|
||||
double mass, double inertiabox, int equality, int rigid, int flatskin,
|
||||
int elastic2d, const double pos[3], const double quat[4],
|
||||
const double origin[3], const char* file, const mjVFS* vfs);
|
||||
|
||||
// Add contact pair.
|
||||
// Nullable: def
|
||||
MJAPI mjsPair* mjs_addPair(mjSpec* s, const mjsDefault* def);
|
||||
|
||||
@@ -10366,6 +10366,144 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
|
||||
),
|
||||
doc='Add flex.',
|
||||
)),
|
||||
('mjs_makeFlex',
|
||||
FunctionDecl(
|
||||
name='mjs_makeFlex',
|
||||
return_type=PointerType(
|
||||
inner_type=ValueType(name='mjsFlex'),
|
||||
),
|
||||
parameters=(
|
||||
FunctionParameterDecl(
|
||||
name='body',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjsBody'),
|
||||
),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='name',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='char', is_const=True),
|
||||
),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='type',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='char', is_const=True),
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='dim',
|
||||
type=ValueType(name='int'),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='dof',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='char', is_const=True),
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='count',
|
||||
type=ArrayType(
|
||||
inner_type=ValueType(name='int', is_const=True),
|
||||
extents=(3,),
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='cellcount',
|
||||
type=ArrayType(
|
||||
inner_type=ValueType(name='int', is_const=True),
|
||||
extents=(3,),
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='spacing',
|
||||
type=ArrayType(
|
||||
inner_type=ValueType(name='double', is_const=True),
|
||||
extents=(3,),
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='scale',
|
||||
type=ArrayType(
|
||||
inner_type=ValueType(name='double', is_const=True),
|
||||
extents=(3,),
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='radius',
|
||||
type=ValueType(name='double'),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='mass',
|
||||
type=ValueType(name='double'),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='inertiabox',
|
||||
type=ValueType(name='double'),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='equality',
|
||||
type=ValueType(name='int'),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='rigid',
|
||||
type=ValueType(name='int'),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='flatskin',
|
||||
type=ValueType(name='int'),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='elastic2d',
|
||||
type=ValueType(name='int'),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='pos',
|
||||
type=ArrayType(
|
||||
inner_type=ValueType(name='double', is_const=True),
|
||||
extents=(3,),
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='quat',
|
||||
type=ArrayType(
|
||||
inner_type=ValueType(name='double', is_const=True),
|
||||
extents=(4,),
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='origin',
|
||||
type=ArrayType(
|
||||
inner_type=ValueType(name='double', is_const=True),
|
||||
extents=(3,),
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='file',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='char', is_const=True),
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='vfs',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjVFS', is_const=True),
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
),
|
||||
doc='Add flexcomp: create flex with auto-generated bodies/joints, return flex spec.', # pylint: disable=line-too-long
|
||||
)),
|
||||
('mjs_addPair',
|
||||
FunctionDecl(
|
||||
name='mjs_addPair',
|
||||
|
||||
@@ -842,6 +842,130 @@ PYBIND11_MODULE(_specs, m) {
|
||||
return FindAllImpl(self, objtype, true);
|
||||
},
|
||||
py::return_value_policy::reference_internal);
|
||||
mjsBody.def(
|
||||
"make_flex",
|
||||
[](raw::MjsBody& self,
|
||||
const std::string& name,
|
||||
std::optional<std::string> type,
|
||||
int dim,
|
||||
std::optional<std::string> dof,
|
||||
std::optional<std::vector<int>> count,
|
||||
std::optional<std::vector<int>> cellcount,
|
||||
std::optional<std::vector<double>> spacing,
|
||||
std::optional<std::vector<double>> scale,
|
||||
double radius,
|
||||
double mass,
|
||||
double inertiabox,
|
||||
int equality,
|
||||
int rigid,
|
||||
int flatskin,
|
||||
int elastic2d,
|
||||
std::optional<std::vector<double>> pos,
|
||||
std::optional<std::vector<double>> quat,
|
||||
std::optional<std::vector<double>> origin,
|
||||
std::optional<std::string> file,
|
||||
MjVfs* vfs) -> raw::MjsFlex* {
|
||||
const char* type_str = type.has_value() ? type->c_str() : nullptr;
|
||||
const char* dof_str = dof.has_value() ? dof->c_str() : nullptr;
|
||||
const char* file_str = file.has_value() ? file->c_str() : nullptr;
|
||||
const mjVFS* vfs_ptr = vfs ? vfs->get() : nullptr;
|
||||
|
||||
int count_arr[3] = {10, 10, 10};
|
||||
if (count.has_value()) {
|
||||
if (count->size() != 3) {
|
||||
throw pybind11::value_error("count must have 3 elements");
|
||||
}
|
||||
for (int i = 0; i < 3; i++) count_arr[i] = (*count)[i];
|
||||
}
|
||||
const int* count_ptr = count.has_value() ? count_arr : nullptr;
|
||||
|
||||
int cellcount_arr[3] = {-1, -1, -1};
|
||||
if (cellcount.has_value()) {
|
||||
if (cellcount->size() != 3) {
|
||||
throw pybind11::value_error("cellcount must have 3 elements");
|
||||
}
|
||||
for (int i = 0; i < 3; i++) cellcount_arr[i] = (*cellcount)[i];
|
||||
}
|
||||
const int* cellcount_ptr =
|
||||
cellcount.has_value() ? cellcount_arr : nullptr;
|
||||
|
||||
double spacing_arr[3] = {0.02, 0.02, 0.02};
|
||||
if (spacing.has_value()) {
|
||||
if (spacing->size() != 3) {
|
||||
throw pybind11::value_error("spacing must have 3 elements");
|
||||
}
|
||||
for (int i = 0; i < 3; i++) spacing_arr[i] = (*spacing)[i];
|
||||
}
|
||||
const double* spacing_ptr = spacing.has_value() ? spacing_arr : nullptr;
|
||||
|
||||
double scale_arr[3] = {1, 1, 1};
|
||||
if (scale.has_value()) {
|
||||
if (scale->size() != 3) {
|
||||
throw pybind11::value_error("scale must have 3 elements");
|
||||
}
|
||||
for (int i = 0; i < 3; i++) scale_arr[i] = (*scale)[i];
|
||||
}
|
||||
const double* scale_ptr = scale.has_value() ? scale_arr : nullptr;
|
||||
|
||||
double pos_arr[3] = {0, 0, 0};
|
||||
if (pos.has_value()) {
|
||||
if (pos->size() != 3) {
|
||||
throw pybind11::value_error("pos must have 3 elements");
|
||||
}
|
||||
for (int i = 0; i < 3; i++) pos_arr[i] = (*pos)[i];
|
||||
}
|
||||
const double* pos_ptr = pos.has_value() ? pos_arr : nullptr;
|
||||
|
||||
double quat_arr[4] = {1, 0, 0, 0};
|
||||
if (quat.has_value()) {
|
||||
if (quat->size() != 4) {
|
||||
throw pybind11::value_error("quat must have 4 elements");
|
||||
}
|
||||
for (int i = 0; i < 4; i++) quat_arr[i] = (*quat)[i];
|
||||
}
|
||||
const double* quat_ptr = quat.has_value() ? quat_arr : nullptr;
|
||||
|
||||
double origin_arr[3] = {0, 0, 0};
|
||||
if (origin.has_value()) {
|
||||
if (origin->size() != 3) {
|
||||
throw pybind11::value_error("origin must have 3 elements");
|
||||
}
|
||||
for (int i = 0; i < 3; i++) origin_arr[i] = (*origin)[i];
|
||||
}
|
||||
const double* origin_ptr = origin.has_value() ? origin_arr : nullptr;
|
||||
|
||||
auto out = mjs_makeFlex(
|
||||
&self, name.c_str(), type_str, dim, dof_str,
|
||||
count_ptr, cellcount_ptr, spacing_ptr, scale_ptr,
|
||||
radius, mass, inertiabox, equality, rigid, flatskin, elastic2d,
|
||||
pos_ptr, quat_ptr, origin_ptr, file_str, vfs_ptr);
|
||||
if (!out) {
|
||||
raw::MjSpec* spec = mjs_getSpec(self.element);
|
||||
throw pybind11::value_error(mjs_getError(spec));
|
||||
}
|
||||
return out;
|
||||
},
|
||||
py::arg("name"),
|
||||
py::arg("type") = py::none(),
|
||||
py::arg("dim") = 3,
|
||||
py::arg("dof") = py::none(),
|
||||
py::arg("count") = py::none(),
|
||||
py::arg("cellcount") = py::none(),
|
||||
py::arg("spacing") = py::none(),
|
||||
py::arg("scale") = py::none(),
|
||||
py::arg("radius") = 0.0,
|
||||
py::arg("mass") = 1.0,
|
||||
py::arg("inertiabox") = 0.005,
|
||||
py::arg("equality") = 0,
|
||||
py::arg("rigid") = 0,
|
||||
py::arg("flatskin") = 0,
|
||||
py::arg("elastic2d") = 0,
|
||||
py::arg("pos") = py::none(),
|
||||
py::arg("quat") = py::none(),
|
||||
py::arg("origin") = py::none(),
|
||||
py::arg("file") = py::none(),
|
||||
py::arg("vfs") = py::none(),
|
||||
py::return_value_policy::reference_internal);
|
||||
mjsBody.def(
|
||||
"find_child",
|
||||
[](raw::MjsBody& self, std::string& name) -> raw::MjsBody* {
|
||||
|
||||
@@ -2032,5 +2032,75 @@ class SpecsTest(absltest.TestCase):
|
||||
with self.assertRaises(mujoco.FatalError):
|
||||
spec.encode(filename, model)
|
||||
|
||||
def test_make_flex_grid(self):
|
||||
# Create a spec with a flexcomp grid.
|
||||
spec = mujoco.MjSpec()
|
||||
body = spec.worldbody.add_body(name='flex_body')
|
||||
flex = body.make_flex(
|
||||
name='test_flex',
|
||||
type='grid',
|
||||
dim=3,
|
||||
count=[4, 4, 4],
|
||||
spacing=[0.05, 0.05, 0.05],
|
||||
mass=0.5,
|
||||
equality=1,
|
||||
)
|
||||
self.assertIsNotNone(flex)
|
||||
model = spec.compile()
|
||||
self.assertIsNotNone(model)
|
||||
self.assertGreater(model.nflex, 0)
|
||||
|
||||
# Verify elastic2d is forwarded to Make() and produces shell-mode
|
||||
# strain constraints (equality=3 + elastic2d=2 triggers shell path).
|
||||
spec2 = mujoco.MjSpec()
|
||||
body2 = spec2.worldbody.add_body(name='shell_body')
|
||||
flex2 = body2.make_flex(
|
||||
name='shell_flex',
|
||||
type='grid',
|
||||
dim=3,
|
||||
count=[3, 3, 3],
|
||||
spacing=[0.1, 0.1, 0.1],
|
||||
dof='trilinear',
|
||||
cellcount=[2, 2, 1],
|
||||
mass=0.5,
|
||||
equality=3, # strain
|
||||
elastic2d=2, # bend
|
||||
)
|
||||
flex2.young = 1e3
|
||||
flex2.thickness = 0.01
|
||||
flex2.selfcollide = mujoco.mjtFlexSelf.mjFLEXSELF_NONE
|
||||
self.assertIsNotNone(flex2)
|
||||
model2 = spec2.compile()
|
||||
self.assertIsNotNone(model2)
|
||||
# Shell mode creates face-based constraints; verify they exist.
|
||||
self.assertGreater(model2.neq, 0)
|
||||
|
||||
def test_make_flex_defaults(self):
|
||||
# Create a spec with minimal flexcomp args (defaults).
|
||||
spec = mujoco.MjSpec()
|
||||
body = spec.worldbody.add_body(name='flex_body')
|
||||
flex = body.make_flex(name='default_flex', equality=1)
|
||||
self.assertIsNotNone(flex)
|
||||
model = spec.compile()
|
||||
self.assertIsNotNone(model)
|
||||
|
||||
def test_make_flex_with_pos_quat(self):
|
||||
# Create a spec with flexcomp that has a pose.
|
||||
spec = mujoco.MjSpec()
|
||||
body = spec.worldbody.add_body(name='flex_body')
|
||||
flex = body.make_flex(
|
||||
name='posed_flex',
|
||||
type='grid',
|
||||
dim=2,
|
||||
count=[3, 3, 1],
|
||||
spacing=[0.1, 0.1, 0.1],
|
||||
pos=[1.0, 2.0, 3.0],
|
||||
quat=[1.0, 0.0, 0.0, 0.0],
|
||||
equality=1,
|
||||
)
|
||||
self.assertIsNotNone(flex)
|
||||
model = spec.compile()
|
||||
self.assertIsNotNone(model)
|
||||
|
||||
if __name__ == '__main__':
|
||||
absltest.main()
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "engine/engine_support.h"
|
||||
#include "user/user_cache.h"
|
||||
#include "user/user_flexcomp.h"
|
||||
#include "user/user_model.h"
|
||||
#include "user/user_objects.h"
|
||||
#include "user/user_resource.h"
|
||||
@@ -599,6 +600,121 @@ mjsFlex* mjs_addFlex(mjSpec* s) {
|
||||
|
||||
|
||||
|
||||
// helper: convert type string to mjtFcompType
|
||||
static mjtFcompType FlexcompTypeFromStr(const char* type) {
|
||||
if (!type || !strcmp(type, "grid")) return mjFCOMPTYPE_GRID;
|
||||
if (!strcmp(type, "box")) return mjFCOMPTYPE_BOX;
|
||||
if (!strcmp(type, "cylinder")) return mjFCOMPTYPE_CYLINDER;
|
||||
if (!strcmp(type, "ellipsoid")) return mjFCOMPTYPE_ELLIPSOID;
|
||||
if (!strcmp(type, "square")) return mjFCOMPTYPE_SQUARE;
|
||||
if (!strcmp(type, "disc")) return mjFCOMPTYPE_DISC;
|
||||
if (!strcmp(type, "circle")) return mjFCOMPTYPE_CIRCLE;
|
||||
if (!strcmp(type, "mesh")) return mjFCOMPTYPE_MESH;
|
||||
if (!strcmp(type, "gmsh")) return mjFCOMPTYPE_GMSH;
|
||||
if (!strcmp(type, "direct")) return mjFCOMPTYPE_DIRECT;
|
||||
return mjFCOMPTYPE_GRID; // default
|
||||
}
|
||||
|
||||
// helper: convert dof string to mjtDof
|
||||
static mjtDof FlexcompDofFromStr(const char* dof) {
|
||||
if (!dof || !strcmp(dof, "full")) return mjFCOMPDOF_FULL;
|
||||
if (!strcmp(dof, "radial")) return mjFCOMPDOF_RADIAL;
|
||||
if (!strcmp(dof, "trilinear")) return mjFCOMPDOF_TRILINEAR;
|
||||
if (!strcmp(dof, "quadratic")) return mjFCOMPDOF_QUADRATIC;
|
||||
if (!strcmp(dof, "2d")) return mjFCOMPDOF_2D;
|
||||
return mjFCOMPDOF_FULL; // default
|
||||
}
|
||||
|
||||
|
||||
// add flexcomp: create flex with auto-generated bodies/joints
|
||||
mjsFlex* mjs_makeFlex(mjsBody* body, const char* name, const char* type, int dim,
|
||||
const char* dof, const int count[3], const int cellcount[3],
|
||||
const double spacing[3], const double scale[3], double radius,
|
||||
double mass, double inertiabox, int equality, int rigid, int flatskin,
|
||||
int elastic2d, const double pos[3], const double quat[4],
|
||||
const double origin[3], const char* file, const mjVFS* vfs) {
|
||||
if (!body || !name) {
|
||||
mju_error("mjs_makeFlex: body and name must not be null");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mjCModel* model = static_cast<mjCBody*>(body->element)->model;
|
||||
|
||||
// create temporary flexcomp with defaults
|
||||
mjCFlexcomp fcomp;
|
||||
fcomp.name = name;
|
||||
fcomp.type = FlexcompTypeFromStr(type);
|
||||
fcomp.doftype = FlexcompDofFromStr(dof);
|
||||
|
||||
// topology
|
||||
if (count) {
|
||||
fcomp.count[0] = count[0];
|
||||
fcomp.count[1] = count[1];
|
||||
fcomp.count[2] = count[2];
|
||||
}
|
||||
if (cellcount) {
|
||||
fcomp.cellcount[0] = cellcount[0];
|
||||
fcomp.cellcount[1] = cellcount[1];
|
||||
fcomp.cellcount[2] = cellcount[2];
|
||||
}
|
||||
if (spacing) {
|
||||
fcomp.spacing[0] = spacing[0];
|
||||
fcomp.spacing[1] = spacing[1];
|
||||
fcomp.spacing[2] = spacing[2];
|
||||
}
|
||||
if (scale) {
|
||||
fcomp.scale[0] = scale[0];
|
||||
fcomp.scale[1] = scale[1];
|
||||
fcomp.scale[2] = scale[2];
|
||||
}
|
||||
if (origin) {
|
||||
fcomp.origin[0] = origin[0];
|
||||
fcomp.origin[1] = origin[1];
|
||||
fcomp.origin[2] = origin[2];
|
||||
}
|
||||
|
||||
// physics
|
||||
fcomp.def.spec.flex->dim = dim;
|
||||
fcomp.def.spec.flex->radius = radius;
|
||||
if (mass > 0) fcomp.mass = mass;
|
||||
if (inertiabox > 0) fcomp.inertiabox = inertiabox;
|
||||
fcomp.equality = equality;
|
||||
fcomp.rigid = rigid;
|
||||
fcomp.def.spec.flex->flatskin = flatskin;
|
||||
fcomp.def.spec.flex->elastic2d = elastic2d;
|
||||
|
||||
// pose
|
||||
if (pos) {
|
||||
fcomp.pos[0] = pos[0];
|
||||
fcomp.pos[1] = pos[1];
|
||||
fcomp.pos[2] = pos[2];
|
||||
}
|
||||
if (quat) {
|
||||
fcomp.quat[0] = quat[0];
|
||||
fcomp.quat[1] = quat[1];
|
||||
fcomp.quat[2] = quat[2];
|
||||
fcomp.quat[3] = quat[3];
|
||||
}
|
||||
|
||||
// file
|
||||
if (file) {
|
||||
fcomp.file = file;
|
||||
}
|
||||
|
||||
// call Make
|
||||
char error[500] = "";
|
||||
if (!fcomp.Make(body, error, sizeof(error), vfs)) {
|
||||
model->SetError(mjCError(nullptr, "%s", error));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// return the flex that was created (last flex in model)
|
||||
mjCFlex* flex = model->Flexes().back();
|
||||
return &flex->spec;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// add frame to body
|
||||
mjsFrame* mjs_addFrame(mjsBody* bodyspec, mjsFrame* parentframe) {
|
||||
mjCFrame* parentframeC = 0;
|
||||
|
||||
@@ -118,6 +118,14 @@ MJAPI mjsSensor* mjs_addSensor(mjSpec* s);
|
||||
// Add flex.
|
||||
MJAPI mjsFlex* mjs_addFlex(mjSpec* s);
|
||||
|
||||
// Add flexcomp: create flex with auto-generated bodies/joints, return flex spec.
|
||||
MJAPI mjsFlex* mjs_makeFlex(mjsBody* body, const char* name, const char* type, int dim,
|
||||
const char* dof, const int count[3], const int cellcount[3],
|
||||
const double spacing[3], const double scale[3], double radius,
|
||||
double mass, double inertiabox, int equality, int rigid, int flatskin,
|
||||
int elastic2d, const double pos[3], const double quat[4],
|
||||
const double origin[3], const char* file, const mjVFS* vfs);
|
||||
|
||||
// Add contact pair.
|
||||
MJAPI mjsPair* mjs_addPair(mjSpec* s, const mjsDefault* def);
|
||||
|
||||
|
||||
@@ -9952,6 +9952,22 @@ int mjs_isWarning_wrapper(MjSpec& s) {
|
||||
return mjs_isWarning(s.get());
|
||||
}
|
||||
|
||||
std::optional<MjsFlex> mjs_makeFlex_wrapper(MjsBody& body, const String& name, const StringOrNull& type, int dim, const StringOrNull& dof, const NumberArray& count, const NumberArray& cellcount, const NumberArray& spacing, const NumberArray& scale, double radius, double mass, double inertiabox, int equality, int rigid, int flatskin, int elastic2d, const NumberArray& pos, const NumberArray& quat, const NumberArray& origin, const StringOrNull& file, const MjVFS& vfs) {
|
||||
CHECK_VAL(name);
|
||||
UNPACK_NULLABLE_ARRAY(int, count);
|
||||
UNPACK_NULLABLE_ARRAY(int, cellcount);
|
||||
UNPACK_NULLABLE_ARRAY(double, spacing);
|
||||
UNPACK_NULLABLE_ARRAY(double, scale);
|
||||
UNPACK_NULLABLE_ARRAY(double, pos);
|
||||
UNPACK_NULLABLE_ARRAY(double, quat);
|
||||
UNPACK_NULLABLE_ARRAY(double, origin);
|
||||
mjsFlex* result = mjs_makeFlex(body.get(), name.as<const std::string>().data(), type.as<const std::string>().data(), dim, dof.as<const std::string>().data(), count_.data(), cellcount_.data(), spacing_.data(), scale_.data(), radius, mass, inertiabox, equality, rigid, flatskin, elastic2d, pos_.data(), quat_.data(), origin_.data(), file.as<const std::string>().data(), vfs.get());
|
||||
if (result == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return MjsFlex(result);
|
||||
}
|
||||
|
||||
int mjs_makeMesh_wrapper(MjsMesh& mesh, mjtMeshBuiltin builtin, const val& params, int nparams) {
|
||||
UNPACK_VALUE(double, params);
|
||||
return mjs_makeMesh(mesh.get(), builtin, params_.data(), nparams);
|
||||
@@ -13474,6 +13490,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
function("mjs_getWrapSideSite", &mjs_getWrapSideSite_wrapper);
|
||||
function("mjs_getWrapTarget", &mjs_getWrapTarget_wrapper);
|
||||
function("mjs_isWarning", &mjs_isWarning_wrapper);
|
||||
function("mjs_makeFlex", &mjs_makeFlex_wrapper);
|
||||
function("mjs_makeMesh", &mjs_makeMesh_wrapper);
|
||||
function("mjs_nextChild", &mjs_nextChild_wrapper);
|
||||
function("mjs_nextElement", &mjs_nextElement_wrapper);
|
||||
|
||||
@@ -143,7 +143,7 @@ def get_param_string(p: ast_nodes.FunctionParameterDecl) -> str:
|
||||
# Pointer to primitive value parameters or arrays
|
||||
if p.type.inner_type.name == "char":
|
||||
if p.nullable:
|
||||
return f"const NullableString& {p.name}"
|
||||
return f"const StringOrNull& {p.name}"
|
||||
else:
|
||||
return f"const String& {p.name}"
|
||||
elif (
|
||||
|
||||
Reference in New Issue
Block a user