Introduce new logging API, fixes #858
PiperOrigin-RevId: 930744288 Change-Id: I6ec1203b55c031390f3eef23192e2337508ce886
This commit is contained in:
committed by
Copybara-Service
parent
a2abaf7aef
commit
58f6d52491
@@ -17,6 +17,7 @@
|
||||
import contextlib
|
||||
import copy
|
||||
from etils import epath
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
|
||||
@@ -511,6 +512,54 @@ class MuJoCoBindingsTest(parameterized.TestCase):
|
||||
mujoco.mj_checkPos(self.model, self.data)
|
||||
self.assertEqual(warnings[mujoco.mjtWarning.mjWARN_BADQPOS].number, 1)
|
||||
|
||||
def test_mju_user_warning_callback_receives_warnings(self):
|
||||
"""Regression test: C warnings must reach Python mju_user_warning callbacks.
|
||||
|
||||
The unified logging API routes all messages through a TLS log handler.
|
||||
This test verifies that non-error messages are forwarded to the global
|
||||
handler chain, where legacy mju_user_warning callbacks are invoked.
|
||||
"""
|
||||
warning_messages = []
|
||||
def warning_cb(msg):
|
||||
warning_messages.append(msg)
|
||||
old_cb = mujoco.get_mju_user_warning()
|
||||
try:
|
||||
mujoco.set_mju_user_warning(warning_cb)
|
||||
# Trigger a C-level warning by setting qpos to NaN and calling mj_step.
|
||||
model = mujoco.MjModel.from_xml_string(TEST_XML)
|
||||
data = mujoco.MjData(model)
|
||||
data.qpos[0] = float('NaN')
|
||||
mujoco.mj_checkPos(model, data)
|
||||
self.assertNotEmpty(warning_messages)
|
||||
# The warning message should mention the bad QPOS value.
|
||||
self.assertTrue(
|
||||
any('QPOS' in msg for msg in warning_messages), warning_messages
|
||||
)
|
||||
finally:
|
||||
mujoco.set_mju_user_warning(old_cb)
|
||||
|
||||
def test_mjtopic_time_cmp_logs_compile_time(self):
|
||||
"""Verifies MjLogConfig.get/set and info topic logging."""
|
||||
old_cfg = mujoco.MjLogConfig.get()
|
||||
log_path = os.path.join(
|
||||
absltest.get_default_test_tmpdir(), 'test_compile.log'
|
||||
)
|
||||
os.makedirs(os.path.dirname(log_path), exist_ok=True)
|
||||
try:
|
||||
cfg = mujoco.MjLogConfig.get()
|
||||
cfg.logto_file = True
|
||||
cfg.logfile = log_path
|
||||
cfg.topics |= (1 << (mujoco.mjtLogTopic.mjTOPIC_TIME_CMP - 1))
|
||||
cfg.set()
|
||||
|
||||
mujoco.MjModel.from_xml_string(TEST_XML)
|
||||
|
||||
with open(log_path, 'r') as f:
|
||||
output = f.read()
|
||||
self.assertIn('compile time', output)
|
||||
finally:
|
||||
old_cfg.set()
|
||||
|
||||
def test_mjcontact_can_copy(self):
|
||||
mujoco.mj_forward(self.model, self.data)
|
||||
|
||||
|
||||
@@ -192,6 +192,7 @@ _ALLOWED_FIXED_ARRAYS = {
|
||||
|
||||
_FUNCTION_POINTER_TYPES = {
|
||||
'mjfItemEnable',
|
||||
'mjfLogHandler',
|
||||
}
|
||||
|
||||
_STRUCT_NAME_OVERRIDES = {}
|
||||
|
||||
@@ -37,10 +37,8 @@ def main(argv: Sequence[str]) -> None:
|
||||
|
||||
struct_decls = []
|
||||
for func in FUNCTIONS.values():
|
||||
# Skip mju_error_{i,s} and mju_warning_{i,s} as these are not
|
||||
# supported in the Python bindings, and Introspect currently
|
||||
# doesn't support variadic functions.
|
||||
if func.name.startswith('mju_error') or func.name == 'mju_warning':
|
||||
# Skip variadic functions as Introspect currently doesn't support them.
|
||||
if func.name in ('mju_error', 'mju_warning', 'mju_info'):
|
||||
continue
|
||||
|
||||
# Modify some parameter types.
|
||||
|
||||
+43
-8
@@ -15,12 +15,15 @@
|
||||
#ifndef MUJOCO_PYTHON_ERRORS_H_
|
||||
#define MUJOCO_PYTHON_ERRORS_H_
|
||||
|
||||
#include <array>
|
||||
#include <csetjmp>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include <mujoco/mjexport.h>
|
||||
#include <mujoco/mjtype.h>
|
||||
#include "private.h"
|
||||
#include "util/crossplatform.h"
|
||||
#include "util/func_wrap.h"
|
||||
@@ -104,9 +107,28 @@ class ErrorBase : public pybind11::builtin_exception {
|
||||
static thread_local std::jmp_buf mju_error_jmp_buf;
|
||||
static thread_local std::array<char, 1024> mju_error_msg{0};
|
||||
|
||||
static inline void MjErrorHandler(const char* msg) {
|
||||
std::strncpy(mju_error_msg.data(), msg, mju_error_msg.size() - 1);
|
||||
mju_error_msg.data()[mju_error_msg.size() - 1] = '\0';
|
||||
// The handler to forward non-error messages to. Set by WrapFunc before each
|
||||
// call into MuJoCo C code, pointing to either the previously installed TLS
|
||||
// handler or the active global handler.
|
||||
static thread_local mjfLogHandler mju_forward_handler = nullptr;
|
||||
|
||||
static inline void MjErrorHandler(const mjLogMessage* msg) {
|
||||
if (msg->level != mjLOG_ERROR) {
|
||||
// Forward warnings, info, and debug messages to the previous handler so
|
||||
// that legacy mju_user_warning callbacks and console output continue to
|
||||
// work.
|
||||
if (mju_forward_handler != nullptr) {
|
||||
mju_forward_handler(msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (msg->func != nullptr) {
|
||||
std::snprintf(mju_error_msg.data(), mju_error_msg.size(), "%s: %s",
|
||||
msg->func, msg->subject);
|
||||
} else {
|
||||
std::strncpy(mju_error_msg.data(), msg->subject, mju_error_msg.size() - 1);
|
||||
mju_error_msg.data()[mju_error_msg.size() - 1] = '\0';
|
||||
}
|
||||
std::longjmp(mju_error_jmp_buf, 1);
|
||||
}
|
||||
|
||||
@@ -121,7 +143,17 @@ struct MjErrorIntercepter {
|
||||
#else
|
||||
return [callable](Args... args) MUJOCO_ALWAYS_INLINE_LAMBDA_MUTABLE {
|
||||
#endif
|
||||
_mjPRIVATE__set_tls_error_fn(&MjErrorHandler);
|
||||
mjfLogHandler prev_handler = _mjPRIVATE_setTlsLogHandler(&MjErrorHandler);
|
||||
|
||||
// Determine the handler to forward non-error messages to.
|
||||
// If there was a previously installed TLS handler, forward to it.
|
||||
// Otherwise, probe the global handler via mju_setLogHandler.
|
||||
mjfLogHandler old_forward = mju_forward_handler;
|
||||
if (prev_handler != nullptr) {
|
||||
mju_forward_handler = prev_handler;
|
||||
} else {
|
||||
mju_forward_handler = _mjPRIVATE_getGlobalLogHandler();
|
||||
}
|
||||
|
||||
// DON'T MIX RAII WITH SETJMP!
|
||||
// From https://en.cppreference.com/w/cpp/utility/program/longjmp:
|
||||
@@ -131,16 +163,19 @@ struct MjErrorIntercepter {
|
||||
if (setjmp(mju_error_jmp_buf) == 0) {
|
||||
if constexpr (std::is_void_v<decltype(callable(args...))>) {
|
||||
callable(args...);
|
||||
_mjPRIVATE__set_tls_error_fn(nullptr);
|
||||
mju_forward_handler = old_forward;
|
||||
_mjPRIVATE_setTlsLogHandler(prev_handler);
|
||||
} else {
|
||||
auto ret = callable(args...);
|
||||
static_assert(std::is_trivially_destructible_v<decltype(ret)>);
|
||||
_mjPRIVATE__set_tls_error_fn(nullptr);
|
||||
mju_forward_handler = old_forward;
|
||||
_mjPRIVATE_setTlsLogHandler(prev_handler);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
// This branch is entered via a longjmp back from our mju_error handler.
|
||||
_mjPRIVATE__set_tls_error_fn(nullptr);
|
||||
mju_forward_handler = old_forward;
|
||||
_mjPRIVATE_setTlsLogHandler(prev_handler);
|
||||
{
|
||||
// Check if a Python callback has thrown an exception.
|
||||
// We cannot use a py::gil_scoped_acquire here: on Windows its
|
||||
|
||||
@@ -609,6 +609,29 @@ ENUMS: Mapping[str, EnumDecl] = dict([
|
||||
('mjS_AWAKE', 1),
|
||||
]),
|
||||
)),
|
||||
('mjtLogLevel',
|
||||
EnumDecl(
|
||||
name='mjtLogLevel',
|
||||
declname='enum mjtLogLevel_',
|
||||
values=dict([
|
||||
('mjLOG_DEBUG', 0),
|
||||
('mjLOG_INFO', 1),
|
||||
('mjLOG_WARNING', 2),
|
||||
('mjLOG_ERROR', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtLogTopic',
|
||||
EnumDecl(
|
||||
name='mjtLogTopic',
|
||||
declname='enum mjtLogTopic_',
|
||||
values=dict([
|
||||
('mjTOPIC_NONE', 0),
|
||||
('mjTOPIC_TIME_STP', 1),
|
||||
('mjTOPIC_TIME_CMP', 2),
|
||||
('mjTOPIC_SLEEP', 3),
|
||||
('mjNTOPIC', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtGeomInertia',
|
||||
EnumDecl(
|
||||
name='mjtGeomInertia',
|
||||
|
||||
@@ -6309,6 +6309,69 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
|
||||
parameters=(),
|
||||
doc='Clear user error and memory handlers.',
|
||||
)),
|
||||
('mju_setLogHandler',
|
||||
FunctionDecl(
|
||||
name='mju_setLogHandler',
|
||||
return_type=ValueType(name='mjfLogHandler'),
|
||||
parameters=(
|
||||
FunctionParameterDecl(
|
||||
name='handler',
|
||||
type=ValueType(name='mjfLogHandler'),
|
||||
),
|
||||
),
|
||||
doc='Set the active log handler; return the previous handler. If handler is NULL, restore the default handler.', # pylint: disable=line-too-long
|
||||
)),
|
||||
('mju_getLogConfig',
|
||||
FunctionDecl(
|
||||
name='mju_getLogConfig',
|
||||
return_type=ValueType(name='mjLogConfig'),
|
||||
parameters=(),
|
||||
doc='Get default handler configuration.',
|
||||
)),
|
||||
('mju_setLogConfig',
|
||||
FunctionDecl(
|
||||
name='mju_setLogConfig',
|
||||
return_type=ValueType(name='void'),
|
||||
parameters=(
|
||||
FunctionParameterDecl(
|
||||
name='config',
|
||||
type=ValueType(name='mjLogConfig'),
|
||||
),
|
||||
),
|
||||
doc='Set default handler configuration.',
|
||||
)),
|
||||
('mju_info',
|
||||
FunctionDecl(
|
||||
name='mju_info',
|
||||
return_type=ValueType(name='void'),
|
||||
parameters=(
|
||||
FunctionParameterDecl(
|
||||
name='topic',
|
||||
type=ValueType(name='int'),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='msg',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='char', is_const=True),
|
||||
),
|
||||
),
|
||||
),
|
||||
doc='Log an info message with optional topic filtering.',
|
||||
)),
|
||||
('mju_message',
|
||||
FunctionDecl(
|
||||
name='mju_message',
|
||||
return_type=ValueType(name='void'),
|
||||
parameters=(
|
||||
FunctionParameterDecl(
|
||||
name='msg',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjLogMessage', is_const=True),
|
||||
),
|
||||
),
|
||||
),
|
||||
doc='Dispatch a structured log message to the active handler.',
|
||||
)),
|
||||
('mju_malloc',
|
||||
FunctionDecl(
|
||||
name='mju_malloc',
|
||||
|
||||
@@ -28,6 +28,92 @@ from .ast_nodes import StructFieldDecl
|
||||
from .ast_nodes import ValueType
|
||||
|
||||
STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
('mjLogMessage',
|
||||
StructDecl(
|
||||
name='mjLogMessage',
|
||||
declname='struct mjLogMessage_',
|
||||
fields=(
|
||||
StructFieldDecl(
|
||||
name='level',
|
||||
type=ValueType(name='int'),
|
||||
doc='mjtLogLevel',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='topic',
|
||||
type=ValueType(name='int'),
|
||||
doc='mjtLogTopic (0 for error/warning/user)',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='subject',
|
||||
type=ArrayType(
|
||||
inner_type=ValueType(name='char'),
|
||||
extents=(1024,),
|
||||
),
|
||||
doc='message subject (one-liner, printf-formatted)',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='body',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='char', is_const=True),
|
||||
),
|
||||
doc='message body (multi-line detail, or NULL)',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='func',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='char', is_const=True),
|
||||
),
|
||||
doc='__func__ or NULL',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='file',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='char', is_const=True),
|
||||
),
|
||||
doc='__FILE__ or NULL',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='line',
|
||||
type=ValueType(name='int'),
|
||||
doc='__LINE__ or 0',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='timestamp',
|
||||
type=ValueType(name='mjtBool'),
|
||||
doc='prepend timestamp to output',
|
||||
),
|
||||
),
|
||||
)),
|
||||
('mjLogConfig',
|
||||
StructDecl(
|
||||
name='mjLogConfig',
|
||||
declname='struct mjLogConfig_',
|
||||
fields=(
|
||||
StructFieldDecl(
|
||||
name='logto_console',
|
||||
type=ValueType(name='mjtBool'),
|
||||
doc='print to console (default: true)',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='logto_file',
|
||||
type=ValueType(name='mjtBool'),
|
||||
doc='print to log file (default: true)',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='logfile',
|
||||
type=ArrayType(
|
||||
inner_type=ValueType(name='char'),
|
||||
extents=(1024,),
|
||||
),
|
||||
doc='log file path (default: "MUJOCO_LOG.TXT")',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='topics',
|
||||
type=ValueType(name='int'),
|
||||
doc='enabled info topic bitmask (default: 0)',
|
||||
),
|
||||
),
|
||||
)),
|
||||
('mjLROpt',
|
||||
StructDecl(
|
||||
name='mjLROpt',
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
|
||||
#include <mujoco/mjdata.h>
|
||||
#include <mujoco/mjexport.h>
|
||||
#include <mujoco/mjtype.h>
|
||||
|
||||
// DO NOT USE THESE FUNCTIONS ELSEWHERE.
|
||||
// They should be regarded as part of MuJoCo's internal implementation detail.
|
||||
extern "C" {
|
||||
MJAPI void _mjPRIVATE__set_tls_error_fn(void (*h)(const char*));
|
||||
MJAPI mjfLogHandler _mjPRIVATE_setTlsLogHandler(mjfLogHandler handler);
|
||||
MJAPI mjfLogHandler _mjPRIVATE_getGlobalLogHandler(void);
|
||||
MJAPI void* mj_arenaAllocByte(mjData* d, int bytes, int alignment);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mjrender.h>
|
||||
#include <mujoco/mjspec.h>
|
||||
#include <mujoco/mjtype.h>
|
||||
#include <mujoco/mjvisualize.h>
|
||||
|
||||
// Type aliases for MuJoCo C structs to allow us refer to consistently refer
|
||||
@@ -72,6 +73,8 @@ using MjVisualMap = decltype(::mjVisual::map);
|
||||
using MjVisualScale = decltype(::mjVisual::scale);
|
||||
using MjVisualRgba = decltype(::mjVisual::rgba);
|
||||
using MjWarningStat = ::mjWarningStat;
|
||||
using MjLogConfig = ::mjLogConfig;
|
||||
using MjLogMessage = ::mjLogMessage;
|
||||
|
||||
// From mjrender.h
|
||||
using MjrRect = ::mjrRect;
|
||||
|
||||
@@ -539,6 +539,96 @@ This is useful for example when the MJB is not available as a file on disk.)"));
|
||||
X(int, number);
|
||||
#undef X
|
||||
|
||||
// ==================== MJLOGCONFIG ==========================================
|
||||
py::class_<MjLogConfigWrapper> mjLogConfig(m, "MjLogConfig");
|
||||
mjLogConfig.def(py::init<>());
|
||||
mjLogConfig.def("__copy__", [](const MjLogConfigWrapper& other) {
|
||||
return MjLogConfigWrapper(other);
|
||||
});
|
||||
mjLogConfig.def("__deepcopy__",
|
||||
[](const MjLogConfigWrapper& other, py::dict) {
|
||||
return MjLogConfigWrapper(other);
|
||||
});
|
||||
DefineStructFunctions(mjLogConfig);
|
||||
mjLogConfig.def_property(
|
||||
"logto_console",
|
||||
[](const MjLogConfigWrapper& d) { return d.get()->logto_console; },
|
||||
[](MjLogConfigWrapper& d, bool rhs) { d.get()->logto_console = rhs; });
|
||||
mjLogConfig.def_property(
|
||||
"logto_file",
|
||||
[](const MjLogConfigWrapper& d) { return d.get()->logto_file; },
|
||||
[](MjLogConfigWrapper& d, bool rhs) { d.get()->logto_file = rhs; });
|
||||
mjLogConfig.def_property(
|
||||
"logfile",
|
||||
[](const MjLogConfigWrapper& d) { return std::string(d.get()->logfile); },
|
||||
[](MjLogConfigWrapper& d, const std::string& rhs) {
|
||||
std::strncpy(d.get()->logfile, rhs.c_str(), 1023);
|
||||
d.get()->logfile[1023] = '\0';
|
||||
});
|
||||
mjLogConfig.def_property(
|
||||
"topics", [](const MjLogConfigWrapper& d) { return d.get()->topics; },
|
||||
[](MjLogConfigWrapper& d, int rhs) { d.get()->topics = rhs; });
|
||||
mjLogConfig.def_static("get", []() {
|
||||
MjLogConfigWrapper wrapper;
|
||||
*wrapper.get() = mju_getLogConfig();
|
||||
return wrapper;
|
||||
});
|
||||
mjLogConfig.def("set", [](const MjLogConfigWrapper& self) {
|
||||
mju_setLogConfig(*self.get());
|
||||
});
|
||||
|
||||
// ==================== MJLOGMESSAGE =========================================
|
||||
py::class_<MjLogMessageWrapper> mjLogMessage(m, "MjLogMessage");
|
||||
mjLogMessage.def(py::init<>());
|
||||
mjLogMessage.def("__copy__", [](const MjLogMessageWrapper& other) {
|
||||
return MjLogMessageWrapper(other);
|
||||
});
|
||||
mjLogMessage.def("__deepcopy__",
|
||||
[](const MjLogMessageWrapper& other, py::dict) {
|
||||
return MjLogMessageWrapper(other);
|
||||
});
|
||||
DefineStructFunctions(mjLogMessage);
|
||||
mjLogMessage.def_property(
|
||||
"level", [](const MjLogMessageWrapper& d) { return d.get()->level; },
|
||||
[](MjLogMessageWrapper& d, int rhs) { d.get()->level = rhs; });
|
||||
mjLogMessage.def_property(
|
||||
"topic", [](const MjLogMessageWrapper& d) { return d.get()->topic; },
|
||||
[](MjLogMessageWrapper& d, int rhs) { d.get()->topic = rhs; });
|
||||
mjLogMessage.def_property(
|
||||
"subject",
|
||||
[](const MjLogMessageWrapper& d) {
|
||||
return std::string(d.get()->subject);
|
||||
},
|
||||
[](MjLogMessageWrapper& d, const std::string& rhs) {
|
||||
std::strncpy(d.get()->subject, rhs.c_str(), 1023);
|
||||
d.get()->subject[1023] = '\0';
|
||||
});
|
||||
mjLogMessage.def_property_readonly(
|
||||
"body",
|
||||
[](const MjLogMessageWrapper& d) -> py::object {
|
||||
if (d.get()->body) return py::str(d.get()->body);
|
||||
return py::none();
|
||||
});
|
||||
mjLogMessage.def_property_readonly(
|
||||
"func",
|
||||
[](const MjLogMessageWrapper& d) -> py::object {
|
||||
if (d.get()->func) return py::str(d.get()->func);
|
||||
return py::none();
|
||||
});
|
||||
mjLogMessage.def_property_readonly(
|
||||
"file",
|
||||
[](const MjLogMessageWrapper& d) -> py::object {
|
||||
if (d.get()->file) return py::str(d.get()->file);
|
||||
return py::none();
|
||||
});
|
||||
mjLogMessage.def_property(
|
||||
"line", [](const MjLogMessageWrapper& d) { return d.get()->line; },
|
||||
[](MjLogMessageWrapper& d, int rhs) { d.get()->line = rhs; });
|
||||
mjLogMessage.def_property(
|
||||
"timestamp",
|
||||
[](const MjLogMessageWrapper& d) { return d.get()->timestamp; },
|
||||
[](MjLogMessageWrapper& d, bool rhs) { d.get()->timestamp = rhs; });
|
||||
|
||||
// ==================== MJTIMERSTAT ==========================================
|
||||
py::class_<MjTimerStatWrapper> mjTimerStat(m, "MjTimerStat");
|
||||
mjTimerStat.def(py::init<>());
|
||||
@@ -960,18 +1050,22 @@ This is useful for example when the MJB is not available as a file on disk.)"));
|
||||
#undef X
|
||||
|
||||
// ==================== MJRVERTEXATTRIBUTE ===================================
|
||||
py::class_<raw::MjrVertexAttribute> mjrVertexAttribute(m, "MjrVertexAttribute");
|
||||
py::class_<raw::MjrVertexAttribute> mjrVertexAttribute(m,
|
||||
"MjrVertexAttribute");
|
||||
mjrVertexAttribute.def(py::init([](int usage, int type) {
|
||||
return raw::MjrVertexAttribute{nullptr, usage, type};
|
||||
}),
|
||||
py::arg("usage") = 0, py::arg("type") = 0);
|
||||
mjrVertexAttribute.def("__copy__",
|
||||
[](const raw::MjrVertexAttribute& other) { return raw::MjrVertexAttribute(other); });
|
||||
mjrVertexAttribute.def("__deepcopy__", [](const raw::MjrVertexAttribute& other, py::dict) {
|
||||
mjrVertexAttribute.def("__copy__", [](const raw::MjrVertexAttribute& other) {
|
||||
return raw::MjrVertexAttribute(other);
|
||||
});
|
||||
mjrVertexAttribute.def("__deepcopy__",
|
||||
[](const raw::MjrVertexAttribute& other, py::dict) {
|
||||
return raw::MjrVertexAttribute(other);
|
||||
});
|
||||
DefineStructFunctions(mjrVertexAttribute);
|
||||
#define X(var) mjrVertexAttribute.def_readwrite(#var, &raw::MjrVertexAttribute::var)
|
||||
#define X(var) \
|
||||
mjrVertexAttribute.def_readwrite(#var, &raw::MjrVertexAttribute::var)
|
||||
X(usage);
|
||||
X(type);
|
||||
#undef X
|
||||
|
||||
@@ -377,6 +377,38 @@ struct is_mj_struct_list<raw::MjWarningStat> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
|
||||
// ==================== MJLOGCONFIG ============================================
|
||||
template <>
|
||||
class MjWrapper<raw::MjLogConfig> : public WrapperBase<raw::MjLogConfig> {
|
||||
public:
|
||||
MjWrapper();
|
||||
MjWrapper(const MjWrapper&);
|
||||
MjWrapper(MjWrapper&&) = default;
|
||||
MjWrapper(raw::MjLogConfig* ptr, pybind11::handle owner);
|
||||
~MjWrapper() = default;
|
||||
};
|
||||
|
||||
using MjLogConfigWrapper = MjWrapper<raw::MjLogConfig>;
|
||||
|
||||
template <>
|
||||
struct enable_if_mj_struct<raw::MjLogConfig> { using type = void; };
|
||||
|
||||
// ==================== MJLOGMESSAGE ===========================================
|
||||
template <>
|
||||
class MjWrapper<raw::MjLogMessage> : public WrapperBase<raw::MjLogMessage> {
|
||||
public:
|
||||
MjWrapper();
|
||||
MjWrapper(const MjWrapper&);
|
||||
MjWrapper(MjWrapper&&) = default;
|
||||
MjWrapper(raw::MjLogMessage* ptr, pybind11::handle owner);
|
||||
~MjWrapper() = default;
|
||||
};
|
||||
|
||||
using MjLogMessageWrapper = MjWrapper<raw::MjLogMessage>;
|
||||
|
||||
template <>
|
||||
struct enable_if_mj_struct<raw::MjLogMessage> { using type = void; };
|
||||
|
||||
// ==================== MJTIMERSTAT ============================================
|
||||
template <>
|
||||
class MjWrapper<raw::MjTimerStat> : public WrapperBase<raw::MjTimerStat> {
|
||||
@@ -977,6 +1009,8 @@ using _impl::MjVisualRgbaWrapper;
|
||||
using _impl::MjVisualWrapper;
|
||||
using _impl::MjStatisticWrapper;
|
||||
using _impl::MjWarningStatWrapper;
|
||||
using _impl::MjLogConfigWrapper;
|
||||
using _impl::MjLogMessageWrapper;
|
||||
using _impl::MjTimerStatWrapper;
|
||||
using _impl::MjSolverStatWrapper;
|
||||
using _impl::MjModelWrapper;
|
||||
|
||||
@@ -1019,6 +1019,28 @@ MjWarningStatList::MjStructList(MjWarningStatList& other, py::slice slice)
|
||||
: StructListBase(other, slice), X(int, lastinfo), X(int, number) {}
|
||||
#undef X
|
||||
|
||||
// ==================== MJLOGCONFIG ============================================
|
||||
MjLogConfigWrapper::MjWrapper() : WrapperBase(new raw::MjLogConfig{}) {}
|
||||
|
||||
MjLogConfigWrapper::MjWrapper(raw::MjLogConfig* ptr, py::handle owner)
|
||||
: WrapperBase(ptr, owner) {}
|
||||
|
||||
MjLogConfigWrapper::MjWrapper(const MjLogConfigWrapper& other)
|
||||
: MjLogConfigWrapper() {
|
||||
*this->ptr_ = *other.ptr_;
|
||||
}
|
||||
|
||||
// ==================== MJLOGMESSAGE ===========================================
|
||||
MjLogMessageWrapper::MjWrapper() : WrapperBase(new raw::MjLogMessage{}) {}
|
||||
|
||||
MjLogMessageWrapper::MjWrapper(raw::MjLogMessage* ptr, py::handle owner)
|
||||
: WrapperBase(ptr, owner) {}
|
||||
|
||||
MjLogMessageWrapper::MjWrapper(const MjLogMessageWrapper& other)
|
||||
: MjLogMessageWrapper() {
|
||||
*this->ptr_ = *other.ptr_;
|
||||
}
|
||||
|
||||
// ==================== MJTIMERSTAT ============================================
|
||||
MjTimerStatWrapper::MjWrapper() : WrapperBase(new raw::MjTimerStat{}) {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user