Introduce mjpEncoder plugin architecture

Add a new mjpEncoder plugin type mirroring the existing mjpDecoder pattern.
Encoders serialize an mjSpec + mjModel to an mjResource for a given format.

New API functions:
- mjp_registerEncoder: globally register an encoder
- mjp_defaultEncoder: zero-initialize an encoder struct
- mjp_findEncoder: look up an encoder by filename extension or content type

The mjfEncode callback takes (mjSpec*, mjModel*, mjVFS*, mjResource*) and
returns 0 on success. Writing to mjResource keeps symmetry with the decoder
reading from mjResource and leaves the door open for writable resource providers.

PiperOrigin-RevId: 889187898
Change-Id: I180771b2255b91dea188ac5e2cdc3a8f0fb85364
This commit is contained in:
Sam Haves
2026-03-25 05:34:48 -07:00
committed by Copybara-Service
parent 2d33b50243
commit f5d3ce3451
15 changed files with 617 additions and 0 deletions
+65
View File
@@ -1580,6 +1580,29 @@ used for opening and reading resources.
.. mujoco-include:: mjpResourceProvider
.. _mjpDecoder:
mjpDecoder
~~~~~~~~~~~~~~~~~~~
This data structure defines a decoder. It contains a set of callbacks used for decoding :ref:`mjResource`
into :ref:`mjSpec`.
.. mujoco-include:: mjpDecoder
.. _mjpEncoder:
mjpEncoder
~~~~~~~~~~~~~~~~~~~
This data structure defines an encoder. It contains a set of callbacks used for encoding of :ref:`mjSpec` and
:ref:`mjModel` into :ref:`mjResource`.
.. mujoco-include:: mjpEncoder
.. _tyFunction:
Function types
@@ -1757,6 +1780,48 @@ This callback is for checking if a resource was modified since it was last read.
Returns positive value if the resource was modified since last open, 0 if resource was not modified,
and negative value if inconclusive.
.. _mjfDecode:
mjfDecode
~~~~~~~~~
.. code-block:: C
typedef mjSpec* (*mjfDecode)(mjResource* resource, const mjVFS* vfs);
This callback is given an opened resource, and is responsible for decoding it into a :ref:`mjSpec`.
Ownership of the resource and the returned spec is responsibility of the caller.
When decoding fails, the callback should return NULL.
.. _mjfCanDecode:
mjfCanDecode
~~~~~~~~~~~~
.. code-block:: C
typedef int (*mjfCanDecode)(const mjResource* resource);
This callback is given an opened resource, and is responsible for returning true if the resource can
be decoded by the :ref:`mjpDecoder<mjpDecoder>`.
.. _mjfEncode:
mjfEncode
~~~~~~~~~
.. code-block:: C
typedef int (*mjfEncode)(const mjSpec* s, const mjModel* m, const mjVFS* vfs,
mjResource* resource);
This callback populates the :ref:`mjResource<mjResource>` `data` member with bytes representing the
given spec in the format associated with the owning plugin. This may be called with the associated
compiled :ref:`mjModel`.
.. _tyNotes:
+46
View File
@@ -59,6 +59,19 @@ Parse spec from a file.
*Nullable:* ``vfs``, ``error``
.. _mj_encode:
`mj_encode <#mj_encode>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mj_encode
Encode spec/model to a file using a registered encoder.
Returns the number of bytes written on success, -1 on failure.
*Nullable:* ``m``, ``vfs``, ``error``
.. _mj_compile:
`mj_compile <#mj_compile>`__
@@ -3255,6 +3268,39 @@ Return the resource provider with the prefix that matches against the resource n
If no match, return NULL.
.. _mjp_registerEncoder:
`mjp_registerEncoder <#mjp_registerEncoder>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjp_registerEncoder
Globally register an encoder. This function is thread-safe.
If an identical mjpEncoder is already registered, this function does nothing.
If a non-identical mjpEncoder with the same name is already registered, an mju_error is raised.
.. _mjp_defaultEncoder:
`mjp_defaultEncoder <#mjp_defaultEncoder>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjp_defaultEncoder
Set default resource encoder definition.
.. _mjp_findEncoder:
`mjp_findEncoder <#mjp_findEncoder>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjp_findEncoder
Return the encoder that matches against the content type or filename extension.
If no match, return NULL.
.. _Thread:
Threads
+4
View File
@@ -25,6 +25,10 @@ General
The polynomial order is defined by the new constant :ref:`mjNPOLY<glNumericSizes>`. A future breaking C-API change
may unify the linear and higher-order coefficients into a single array.
- Introduced :ref:`mjpEncoder`, the counterpart to :ref:`mjpDecoder` for encoding of :ref:`mjSpec` and :ref:`mjModel` into :ref:`mjResource`.
- Added :ref:`mj_encode`, :ref:`mjp_registerEncoder`, :ref:`mjp_defaultEncoder`, and :ref:`mjp_findEncoder`.
.. admonition:: Breaking API changes
:class: attention
+13
View File
@@ -1686,6 +1686,13 @@ struct mjpDecoder {
// for cleaning it up
};
typedef struct mjpDecoder mjpDecoder;
struct mjpEncoder {
const char* content_type;
const char* extension;
mjfEncode encode;
mjfCloseResource close_resource;
};
typedef struct mjpEncoder mjpEncoder;
typedef enum mjtPluginCapabilityBit_ {
mjPLUGIN_ACTUATOR = 1<<0, // actuator forces
mjPLUGIN_SENSOR = 1<<1, // sensor measurements
@@ -3155,6 +3162,9 @@ mjSpec* mj_parseXML(const char* filename, const mjVFS* vfs, char* error, int err
mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error, int error_sz);
mjSpec* mj_parse(const char* filename, const char* content_type,
const mjVFS* vfs, char* error, int error_sz);
int mj_encode(const mjSpec* s, const mjModel* m, const char* filename,
const char* content_type, const mjVFS* vfs, char* error,
int error_sz);
mjModel* mj_compile(mjSpec* s, const mjVFS* vfs);
int mj_copyBack(mjSpec* s, const mjModel* m);
int mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
@@ -3591,6 +3601,9 @@ const mjpResourceProvider* mjp_getResourceProviderAtSlot(int slot);
void mjp_registerDecoder(const mjpDecoder* decoder);
void mjp_defaultDecoder(mjpDecoder* decoder);
const mjpDecoder* mjp_findDecoder(const mjResource* resource, const char* content_type);
void mjp_registerEncoder(const mjpEncoder* encoder);
void mjp_defaultEncoder(mjpEncoder* encoder);
const mjpEncoder* mjp_findEncoder(const char* filename, const char* content_type);
mjResource* mju_openResource(const char* dir, const char* name,
const mjVFS* vfs, char* error, size_t nerror);
void mju_closeResource(mjResource* resource);
+13
View File
@@ -89,6 +89,19 @@ struct mjpDecoder {
};
typedef struct mjpDecoder mjpDecoder;
//---------------------------------- Encoder -------------------------------------------------------
typedef int (*mjfEncode)(const mjSpec* s, const mjModel* m, const mjVFS* vfs,
mjResource* resource);
struct mjpEncoder {
const char* content_type;
const char* extension;
mjfEncode encode;
mjfCloseResource close_resource;
};
typedef struct mjpEncoder mjpEncoder;
//---------------------------------- Plugins -------------------------------------------------------
typedef enum mjtPluginCapabilityBit_ {
+20
View File
@@ -135,6 +135,13 @@ MJAPI mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error,
MJAPI mjSpec* mj_parse(const char* filename, const char* content_type,
const mjVFS* vfs, char* error, int error_sz);
// Encode spec/model to a file using a registered encoder.
// Returns the number of bytes written on success, -1 on failure.
// Nullable: m, vfs, error
MJAPI int mj_encode(const mjSpec* s, const mjModel* m, const char* filename,
const char* content_type, const mjVFS* vfs, char* error,
int error_sz);
// Compile spec to model.
// Nullable: vfs
MJAPI mjModel* mj_compile(mjSpec* s, const mjVFS* vfs);
@@ -1522,6 +1529,19 @@ MJAPI void mjp_defaultDecoder(mjpDecoder* decoder);
// If no match, return NULL.
MJAPI const mjpDecoder* mjp_findDecoder(const mjResource* resource, const char* content_type);
// Globally register an encoder. This function is thread-safe.
// If an identical mjpEncoder is already registered, this function does nothing.
// If a non-identical mjpEncoder with the same name is already registered, an mju_error is raised.
MJAPI void mjp_registerEncoder(const mjpEncoder* encoder);
// Set default resource encoder definition.
MJAPI void mjp_defaultEncoder(mjpEncoder* encoder);
// Return the encoder that matches against the content type or filename extension.
// If no match, return NULL.
MJAPI const mjpEncoder* mjp_findEncoder(const char* filename, const char* content_type);
//---------------------------------- Resources -----------------------------------------------------
@@ -209,6 +209,7 @@ _OPAQUE_STRUCTS = [
'mjTask',
'mjThreadPool',
'mjpDecoder',
'mjpEncoder',
'mjpResourceProvider',
'mjsElement',
'mjString',
@@ -41,6 +41,8 @@ _ANONYMOUS_KEY_PATTERN = re.compile(r'\d+:\d+(?=\))')
_EXCLUDED = (
'mjpDecoder',
'mjpDecoder_',
'mjpEncoder',
'mjpEncoder_',
'mjpPlugin',
'mjpPlugin_',
'mjpResourceProvider',
+101
View File
@@ -387,6 +387,57 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
doc='Parse spec from a file.',
)),
('mj_encode',
FunctionDecl(
name='mj_encode',
return_type=ValueType(name='int'),
parameters=(
FunctionParameterDecl(
name='s',
type=PointerType(
inner_type=ValueType(name='mjSpec', is_const=True),
),
),
FunctionParameterDecl(
name='m',
type=PointerType(
inner_type=ValueType(name='mjModel', is_const=True),
),
nullable=True,
),
FunctionParameterDecl(
name='filename',
type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
),
FunctionParameterDecl(
name='content_type',
type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
),
FunctionParameterDecl(
name='vfs',
type=PointerType(
inner_type=ValueType(name='mjVFS', is_const=True),
),
nullable=True,
),
FunctionParameterDecl(
name='error',
type=PointerType(
inner_type=ValueType(name='char'),
),
nullable=True,
),
FunctionParameterDecl(
name='error_sz',
type=ValueType(name='int'),
),
),
doc='Encode spec/model to a file using a registered encoder. Returns the number of bytes written on success, -1 on failure.', # pylint: disable=line-too-long
)),
('mj_compile',
FunctionDecl(
name='mj_compile',
@@ -9600,6 +9651,56 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
doc='Return the resource provider with the prefix that matches against the resource name. If no match, return NULL.', # pylint: disable=line-too-long
)),
('mjp_registerEncoder',
FunctionDecl(
name='mjp_registerEncoder',
return_type=ValueType(name='void'),
parameters=(
FunctionParameterDecl(
name='encoder',
type=PointerType(
inner_type=ValueType(name='mjpEncoder', is_const=True),
),
),
),
doc='Globally register an encoder. This function is thread-safe. If an identical mjpEncoder is already registered, this function does nothing. If a non-identical mjpEncoder with the same name is already registered, an mju_error is raised.', # pylint: disable=line-too-long
)),
('mjp_defaultEncoder',
FunctionDecl(
name='mjp_defaultEncoder',
return_type=ValueType(name='void'),
parameters=(
FunctionParameterDecl(
name='encoder',
type=PointerType(
inner_type=ValueType(name='mjpEncoder'),
),
),
),
doc='Set default resource encoder definition.',
)),
('mjp_findEncoder',
FunctionDecl(
name='mjp_findEncoder',
return_type=PointerType(
inner_type=ValueType(name='mjpEncoder', is_const=True),
),
parameters=(
FunctionParameterDecl(
name='filename',
type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
),
FunctionParameterDecl(
name='content_type',
type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
),
),
doc='Return the encoder that matches against the content type or filename extension. If no match, return NULL.', # pylint: disable=line-too-long
)),
('mju_openResource',
FunctionDecl(
name='mju_openResource',
+148
View File
@@ -357,6 +357,85 @@ bool GlobalTable<mjpDecoder>::CopyObject(mjpDecoder& dst, const mjpDecoder& src,
return true;
}
template <>
const char* GlobalTable<mjpEncoder>::HumanReadableTypeName() {
return "resource encoder";
}
template <>
std::string_view GlobalTable<mjpEncoder>::ObjectKey(const mjpEncoder& encoder) {
if (encoder.content_type) {
if (int len = strklen(encoder.content_type); len != -1) {
return std::string_view(encoder.content_type, len);
}
}
return std::string_view(encoder.extension, strklen(encoder.extension));
}
template <>
bool GlobalTable<mjpEncoder>::ObjectEqual(const mjpEncoder& e1,
const mjpEncoder& e2) {
bool content_type_match = false;
if (e1.content_type && e2.content_type) {
content_type_match = CaseInsensitiveEqual(e1.content_type, e2.content_type);
} else {
content_type_match = (e1.content_type == e2.content_type);
}
bool extension_match = false;
if (e1.extension && e2.extension) {
extension_match = CaseInsensitiveEqual(e1.extension, e2.extension);
} else {
extension_match = (e1.extension == e2.extension);
}
return content_type_match && extension_match
&& e1.encode == e2.encode && e1.close_resource == e2.close_resource;
}
template <>
bool GlobalTable<mjpEncoder>::CopyObject(mjpEncoder& dst, const mjpEncoder& src, ErrorMessage& err) {
dst = src;
dst.content_type = nullptr;
dst.extension = nullptr;
if (src.content_type) {
std::unique_ptr<char[]> content_type = CopyName(src.content_type);
if (!content_type) {
if (strklen(src.content_type) == -1) {
std::snprintf(
err, sizeof(err),
"encoder->content_type length exceeds the maximum limit of %d",
kMaxNameLength);
} else {
std::snprintf(err, sizeof(err), "failed to allocate memory for encoder content_type");
}
return false;
}
dst.content_type = content_type.release();
}
if (src.extension) {
std::unique_ptr<char[]> extension = CopyName(src.extension);
if (!extension) {
if (strklen(src.extension) == -1) {
std::snprintf(
err, sizeof(err),
"encoder->extension length exceeds the maximum limit of %d",
kMaxNameLength);
} else {
std::snprintf(err, sizeof(err), "failed to allocate memory for encoder extension");
}
return false;
}
dst.extension = extension.release();
}
return true;
}
// globally register a plugin (thread-safe), return new slot id
int mjp_registerPlugin(const mjpPlugin* plugin) {
if (!plugin->name) {
@@ -539,6 +618,75 @@ const mjpDecoder* mjp_findDecoder(const mjResource* resource, const char* conten
return nullptr;
}
void mjp_registerEncoder(const mjpEncoder* encoder) {
if (!encoder->encode) {
mju_warning("encoder must provide an encode callback.");
return;
}
if (!encoder->close_resource) {
mju_warning("encoder must provide a close_resource callback.");
return;
}
if (!encoder->content_type && !encoder->extension) {
mju_warning("encoder must provide content_type and/or extensions.");
return;
}
mjpEncoder encoder_copy = *encoder;
if (encoder->content_type) {
encoder_copy.extension = nullptr;
GlobalTable<mjpEncoder>::GetSingleton().AppendIfUnique(encoder_copy);
}
if (encoder->extension) {
encoder_copy.content_type = nullptr;
std::string extensions_str(encoder->extension);
std::stringstream ss(extensions_str);
std::string extension;
while (std::getline(ss, extension, '|')) {
if (!extension.empty()) {
encoder_copy.extension = extension.c_str();
GlobalTable<mjpEncoder>::GetSingleton().AppendIfUnique(encoder_copy);
}
}
}
}
void mjp_defaultEncoder(mjpEncoder* encoder) {
std::memset(encoder, 0, sizeof(*encoder));
}
const mjpEncoder* mjp_findEncoder(const char* filename,
const char* content_type) {
auto extension = getext(filename ? filename : "");
bool has_content_type = content_type && strklen(content_type) > 0;
if (!has_content_type && extension.empty()) {
mju_warning("Must provide extension or content_type to mjp_findEncoder.");
return nullptr;
}
if (has_content_type) {
auto* encoder =
GlobalTable<mjpEncoder>::GetSingleton().GetByKey(content_type, nullptr);
if (encoder) {
return encoder;
}
}
if (!extension.empty()) {
auto* encoder = GlobalTable<mjpEncoder>::GetSingleton().GetByKey(
extension.c_str(), nullptr);
if (encoder) {
return encoder;
}
}
return nullptr;
}
// load plugins from a dynamic library
void mj_loadPluginLibrary(const char* path) {
#if defined(_WIN32) || defined(__CYGWIN__)
+9
View File
@@ -71,6 +71,15 @@ MJAPI void mjp_defaultDecoder(mjpDecoder* decoder);
// find a decoder that can process a given resource
MJAPI const mjpDecoder* mjp_findDecoder(const mjResource* resource, const char* content_type);
// registers a resource encoder
MJAPI void mjp_registerEncoder(const mjpEncoder* encoder);
// set default encoder definition
MJAPI void mjp_defaultEncoder(mjpEncoder* encoder);
// find an encoder that can write a given format
MJAPI const mjpEncoder* mjp_findEncoder(const char* filename, const char* content_type);
// =================================================================================================
// MuJoCo-internal functions beyond this point.
// "Unsafe" suffix indicates that improper use of these functions may result in data races.
+53
View File
@@ -16,6 +16,8 @@
#include <algorithm>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iterator>
@@ -136,6 +138,57 @@ mjSpec* mj_parse(const char* filename, const char* content_type,
return spec;
}
// encode spec/model to file
int mj_encode(const mjSpec* s, const mjModel* m, const char* filename,
const char* content_type, const mjVFS* vfs, char* error,
int error_sz) {
const mjpEncoder* encoder = mjp_findEncoder(filename, content_type);
if (!encoder) {
if (error) {
strncpy(error, "no encoder found", error_sz);
error[error_sz - 1] = '\0';
}
return -1;
}
mjResource resource;
memset(&resource, 0, sizeof(resource));
resource.name = const_cast<char*>(filename);
const int nbytes = encoder->encode(s, m, vfs, &resource);
if (nbytes < 0 || !resource.data) {
if (error) {
strncpy(error, "encoder failed", error_sz);
error[error_sz - 1] = '\0';
}
return -1;
}
FILE* fp = fopen(filename, "wb");
if (!fp) {
std::free(resource.data);
if (error) {
strncpy(error, "could not open file for writing", error_sz);
error[error_sz - 1] = '\0';
}
return -1;
}
const std::size_t written = fwrite(resource.data, 1, nbytes, fp);
fclose(fp);
encoder->close_resource(&resource);
if (static_cast<int>(written) != nbytes) {
if (error) {
strncpy(error, "failed to write all bytes to file", error_sz);
error[error_sz - 1] = '\0';
}
return -1;
}
return nbytes;
}
// compile model
mjModel* mj_compile(mjSpec* s, const mjVFS* vfs) {
mjCModel* modelC = static_cast<mjCModel*>(s->element);
+134
View File
@@ -0,0 +1,134 @@
// Copyright 2025 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.
// Tests for encoder plugins.
#include <cstdio>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjplugin.h>
#include <mujoco/mujoco.h>
#include "test/fixture.h"
namespace mujoco {
namespace {
struct FakeEncoderOutput {
int nbody;
int ngeom;
int njnt;
char resource_name[512];
};
int FakeEncode(const mjSpec* s, const mjModel* m, const mjVFS* vfs,
mjResource* resource) {
auto* output = new FakeEncoderOutput;
output->nbody = m->nbody;
output->ngeom = m->ngeom;
output->njnt = m->njnt;
std::snprintf(output->resource_name, sizeof(output->resource_name), "%s",
resource->name);
resource->data = output;
return sizeof(FakeEncoderOutput);
}
void CloseResource(mjResource* resource) {
delete static_cast<FakeEncoderOutput*>(resource->data);
delete resource;
}
mjpEncoder FakeEncoder() {
mjpEncoder encoder;
mjp_defaultEncoder(&encoder);
encoder.content_type = "model/fakeformat";
encoder.extension = ".fakeformat|.alsoFakeFormat";
encoder.encode = FakeEncode;
encoder.close_resource = CloseResource;
return encoder;
}
using EncoderPluginTest = MujocoTest;
TEST_F(EncoderPluginTest, RegisterAndFindByExtension) {
mjpEncoder encoder = FakeEncoder();
mjp_registerEncoder(&encoder);
const mjpEncoder* found = mjp_findEncoder("output.fakeformat", nullptr);
ASSERT_THAT(found, testing::NotNull());
EXPECT_EQ(found->encode, FakeEncode);
}
TEST_F(EncoderPluginTest, FindByAlternateExtension) {
const mjpEncoder* found = mjp_findEncoder("output.alsoFakeFormat", nullptr);
ASSERT_THAT(found, testing::NotNull());
EXPECT_EQ(found->encode, FakeEncode);
}
TEST_F(EncoderPluginTest, FindByContentType) {
const mjpEncoder* found = mjp_findEncoder(nullptr, "model/fakeformat");
ASSERT_THAT(found, testing::NotNull());
EXPECT_EQ(found->encode, FakeEncode);
}
TEST_F(EncoderPluginTest, FindUnknownExtensionReturnsNull) {
const mjpEncoder* found = mjp_findEncoder("output.unknown", nullptr);
EXPECT_THAT(found, testing::IsNull());
}
TEST_F(EncoderPluginTest, DefaultEncoderIsZeroed) {
mjpEncoder encoder;
mjp_defaultEncoder(&encoder);
EXPECT_EQ(encoder.content_type, nullptr);
EXPECT_EQ(encoder.extension, nullptr);
EXPECT_EQ(encoder.encode, nullptr);
}
TEST_F(EncoderPluginTest, EncodeModel) {
mjSpec* spec = mj_makeSpec();
mjsBody* world = mjs_findBody(spec, "world");
mjsBody* body = mjs_addBody(world, nullptr);
mjsGeom* geom = mjs_addGeom(body, nullptr);
geom->size[0] = 1.0;
geom->size[1] = 1.0;
geom->size[2] = 1.0;
mjModel* model = mj_compile(spec, nullptr);
ASSERT_THAT(model, testing::NotNull());
const mjpEncoder* found = mjp_findEncoder("output.fakeformat", nullptr);
ASSERT_THAT(found, testing::NotNull());
mjResource resource = {};
resource.name = const_cast<char*>("output.fakeformat");
int result = found->encode(spec, model, nullptr, &resource);
EXPECT_GT(result, 0);
auto* output = static_cast<FakeEncoderOutput*>(resource.data);
ASSERT_THAT(output, testing::NotNull());
EXPECT_EQ(output->nbody, 2);
EXPECT_EQ(output->ngeom, 1);
EXPECT_EQ(output->njnt, 0);
EXPECT_STREQ(output->resource_name, "output.fakeformat");
delete output;
mj_deleteModel(model);
mj_deleteSpec(spec);
}
} // namespace
} // namespace mujoco
+3
View File
@@ -6700,6 +6700,9 @@ public static unsafe extern void mj_clearCache(mjCache_* cache);
[DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern mjModel_* mj_loadXML([MarshalAs(UnmanagedType.LPStr)]string filename, void* vfs, StringBuilder error, int error_sz);
[DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern int mj_encode(void* s, mjModel_* m, [MarshalAs(UnmanagedType.LPStr)]string filename, [MarshalAs(UnmanagedType.LPStr)]string content_type, void* vfs, StringBuilder error, int error_sz);
[DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern int mj_saveLastXML([MarshalAs(UnmanagedType.LPStr)]string filename, mjModel_* m, StringBuilder error, int error_sz);
+5
View File
@@ -46,15 +46,18 @@ _SKIPPED_PLUGIN_FUNCTIONS: tuple[str, ...] = (
"mjc_getSDF",
"mjc_gradient",
"mjp_defaultDecoder",
"mjp_defaultEncoder",
"mjp_defaultPlugin",
"mjp_defaultResourceProvider",
"mjp_findDecoder",
"mjp_findEncoder",
"mjp_getPlugin",
"mjp_getPluginAtSlot",
"mjp_getResourceProvider",
"mjp_getResourceProviderAtSlot",
"mjp_pluginCount",
"mjp_registerDecoder",
"mjp_registerEncoder",
"mjp_registerPlugin",
"mjp_registerResourceProvider",
"mjp_resourceProviderCount",
@@ -74,6 +77,8 @@ _SKIPPED_CLASS_METHODS: tuple[str, ...] = (
"mj_deleteModel",
"mj_deleteSpec",
"mj_deleteVFS",
"mj_encode",
"mj_parse", # TODO(manevi): Bind this function.
"mj_loadModel",
"mj_loadXML",
"mj_makeData",