Refactor compiler warning handling.
Compiler warnings are now accumulated in a vector of strings within the mjSpec object. New API functions `mjs_numWarnings` and `mjs_getWarning` are added to access these warnings. The compiler's log handler now chains warnings to the global log handler, ensuring they are still displayed immediately. Call sites in `mj_loadXML`, `mj_compile`, and the Python and WASM bindings have been updated to use the new warning API. PiperOrigin-RevId: 933361650 Change-Id: I47cab98a460c57b0898c0a1a43fce2a5b9648eb1
This commit is contained in:
committed by
Copybara-Service
parent
55c6332f20
commit
6f8bb5ef55
@@ -2161,7 +2161,25 @@ Get compiler timing diagnostics from spec, returns pointer to array of size mjNC
|
||||
|
||||
.. mujoco-include:: mjs_isWarning
|
||||
|
||||
Return 1 if compiler error is a warning.
|
||||
Return 1 if compiler error is a warning. Deprecated: use mjs_numWarnings(s) > 0.
|
||||
|
||||
.. _mjs_numWarnings:
|
||||
|
||||
`mjs_numWarnings <#mjs_numWarnings>`__
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. mujoco-include:: mjs_numWarnings
|
||||
|
||||
Get number of warnings accumulated in the spec.
|
||||
|
||||
.. _mjs_getWarning:
|
||||
|
||||
`mjs_getWarning <#mjs_getWarning>`__
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. mujoco-include:: mjs_getWarning
|
||||
|
||||
Get the i-th warning message (returns nullptr if index out of bounds).
|
||||
|
||||
.. _Miscellaneous:
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ General
|
||||
- New types: :ref:`mjtLogLevel`, :ref:`mjtLogTopic`, :ref:`mjLogMessage`, :ref:`mjLogConfig`.
|
||||
- The legacy callbacks :ref:`mju_user_error` and :ref:`mju_user_warning` are deprecated but remain functional.
|
||||
|
||||
- Added :ref:`mjs_numWarnings` and :ref:`mjs_getWarning` for retrieving all warnings accumulated during model
|
||||
compilation and attachment. Deprecated :ref:`mjs_isWarning` in favor of ``mjs_numWarnings(s) > 0``.
|
||||
- Improved primal solver convergence under float32. Improvements initially proposed by :github:user:`n3b` in
|
||||
:issue:`2313` and :github:user:`denzeler-nvidia` in :doc:`MJWarp <mjwarp/index>` pull request
|
||||
`1374 <https://github.com/google-deepmind/mujoco_warp/pull/1374>`__.
|
||||
|
||||
@@ -3555,6 +3555,8 @@ void mju_writeLog(const char* type, const char* msg);
|
||||
const char* mjs_getError(mjSpec* s);
|
||||
const double* mjs_getTimer(mjSpec* s);
|
||||
int mjs_isWarning(mjSpec* s);
|
||||
int mjs_numWarnings(const mjSpec* spec);
|
||||
const char* mjs_getWarning(const mjSpec* spec, int index);
|
||||
void mju_zero3(mjtNum res[3]);
|
||||
void mju_copy3(mjtNum res[3], const mjtNum data[3]);
|
||||
void mju_scl3(mjtNum res[3], const mjtNum vec[3], mjtNum scl);
|
||||
|
||||
@@ -1015,9 +1015,14 @@ MJAPI const char* mjs_getError(mjSpec* s);
|
||||
// Get compiler timing diagnostics from spec, returns pointer to array of size mjNCTIMER.
|
||||
MJAPI const double* mjs_getTimer(mjSpec* s);
|
||||
|
||||
// Return 1 if compiler error is a warning.
|
||||
// Return 1 if compiler error is a warning. Deprecated: use mjs_numWarnings(s) > 0.
|
||||
MJAPI int mjs_isWarning(mjSpec* s);
|
||||
|
||||
// Get number of warnings accumulated in the spec.
|
||||
MJAPI int mjs_numWarnings(const mjSpec* spec);
|
||||
|
||||
// Get the i-th warning message (returns nullptr if index out of bounds).
|
||||
MJAPI const char* mjs_getWarning(const mjSpec* spec, int index);
|
||||
|
||||
//---------------------------------- Standard math -------------------------------------------------
|
||||
|
||||
|
||||
@@ -6486,7 +6486,41 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
|
||||
),
|
||||
),
|
||||
),
|
||||
doc='Return 1 if compiler error is a warning.',
|
||||
doc='Return 1 if compiler error is a warning. Deprecated: use mjs_numWarnings(s) > 0.', # pylint: disable=line-too-long
|
||||
)),
|
||||
('mjs_numWarnings',
|
||||
FunctionDecl(
|
||||
name='mjs_numWarnings',
|
||||
return_type=ValueType(name='int'),
|
||||
parameters=(
|
||||
FunctionParameterDecl(
|
||||
name='spec',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjSpec', is_const=True),
|
||||
),
|
||||
),
|
||||
),
|
||||
doc='Get number of warnings accumulated in the spec.',
|
||||
)),
|
||||
('mjs_getWarning',
|
||||
FunctionDecl(
|
||||
name='mjs_getWarning',
|
||||
return_type=PointerType(
|
||||
inner_type=ValueType(name='char', is_const=True),
|
||||
),
|
||||
parameters=(
|
||||
FunctionParameterDecl(
|
||||
name='spec',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjSpec', is_const=True),
|
||||
),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
name='index',
|
||||
type=ValueType(name='int'),
|
||||
),
|
||||
),
|
||||
doc='Get the i-th warning message (returns nullptr if index out of bounds).', # pylint: disable=line-too-long
|
||||
)),
|
||||
('mju_zero3',
|
||||
FunctionDecl(
|
||||
|
||||
@@ -597,6 +597,18 @@ class SpecsTest(absltest.TestCase):
|
||||
with self.assertRaisesRegex(ValueError, expected_error):
|
||||
spec.compile()
|
||||
|
||||
def test_compile_warnings(self):
|
||||
xml = """
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<flexcomp name="my_flex" type="grid" count="3 3 1" spacing=".05 .05 .05" radius=".01" dim="2"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
"""
|
||||
spec = mujoco.MjSpec.from_string(xml)
|
||||
with self.assertWarnsRegex(UserWarning, 'is not rigid'):
|
||||
spec.compile()
|
||||
|
||||
def test_recompile(self):
|
||||
# Create a spec.
|
||||
spec = mujoco.MjSpec()
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "errors.h"
|
||||
#include "indexers.h" // IWYU pragma: keep
|
||||
#include "private.h"
|
||||
#include "raw.h"
|
||||
#include "structs.h" // IWYU pragma: keep
|
||||
#include <pybind11/cast.h>
|
||||
@@ -118,7 +119,14 @@ raw::MjModel* MjSpec::Compile(mjVFS* vfs) {
|
||||
raw::MjModel* m;
|
||||
{
|
||||
py::gil_scoped_release no_gil;
|
||||
|
||||
// Install a no-op handler to suppress stderr output from warnings.
|
||||
// Compile() installs its own (setjmp/longjmp) handler and then chains to
|
||||
// prev. We want to raise a `warnings.warn`, so pass in a no-op handler.
|
||||
mjfLogHandler prev =
|
||||
_mjPRIVATE_setTlsLogHandler([](const mjLogMessage*) {});
|
||||
m = mj_compile(ptr, vfs);
|
||||
_mjPRIVATE_setTlsLogHandler(prev);
|
||||
}
|
||||
|
||||
if (local_vfs.has_value()) {
|
||||
@@ -127,9 +135,17 @@ raw::MjModel* MjSpec::Compile(mjVFS* vfs) {
|
||||
local_vfs = std::nullopt;
|
||||
}
|
||||
|
||||
if (!m || mjs_isWarning(ptr)) {
|
||||
if (!m) {
|
||||
throw py::value_error(mjs_getError(ptr));
|
||||
}
|
||||
|
||||
int num_warnings = mjs_numWarnings(ptr);
|
||||
if (num_warnings > 0) {
|
||||
py::object warnings = py::module_::import("warnings");
|
||||
for (int i = 0; i < num_warnings; ++i) {
|
||||
warnings.attr("warn")(mjs_getWarning(ptr, i));
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
+26
-4
@@ -483,15 +483,37 @@ const double* mjs_getTimer(mjSpec* s) {
|
||||
return modelC->timer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// check if model has warnings
|
||||
// check if model has warnings (but no error)
|
||||
// TODO(tassa): delete this function
|
||||
int mjs_isWarning(mjSpec* s) {
|
||||
if (!s) {
|
||||
return 0;
|
||||
}
|
||||
mjCModel* modelC = static_cast<mjCModel*>(s->element);
|
||||
return modelC->GetError().warning;
|
||||
return modelC->GetError().message[0] == '\0' &&
|
||||
!modelC->GetWarnings().empty();
|
||||
}
|
||||
|
||||
// get number of warnings
|
||||
int mjs_numWarnings(const mjSpec* spec) {
|
||||
if (!spec) {
|
||||
return 0;
|
||||
}
|
||||
const mjCModel* modelC = static_cast<const mjCModel*>(spec->element);
|
||||
return static_cast<int>(modelC->GetWarnings().size());
|
||||
}
|
||||
|
||||
// get the i-th warning message
|
||||
const char* mjs_getWarning(const mjSpec* spec, int index) {
|
||||
if (!spec) {
|
||||
return nullptr;
|
||||
}
|
||||
const mjCModel* modelC = static_cast<const mjCModel*>(spec->element);
|
||||
if (index < 0 || index >= static_cast<int>(modelC->GetWarnings().size())) {
|
||||
return nullptr;
|
||||
}
|
||||
return modelC->GetWarnings()[index].c_str();
|
||||
}
|
||||
|
||||
// delete model
|
||||
void mj_deleteSpec(mjSpec* s) {
|
||||
|
||||
@@ -1546,7 +1546,7 @@ void mjCMesh::Process() {
|
||||
for (int i = 0; i < nface(); i++) {
|
||||
SetBoundingVolume(i, dvert.data());
|
||||
}
|
||||
tree_.CreateBVH();
|
||||
tree_.CreateBVH(model, this);
|
||||
}
|
||||
mesh_timer_[mjCTIMER_MESH_BVH] += Seconds(Clock::now() - t0).count();
|
||||
|
||||
@@ -5451,7 +5451,7 @@ void mjCFlex::CreateBVH() {
|
||||
|
||||
// create hierarchy
|
||||
tree.RemoveInactiveVolumes(nbvh);
|
||||
tree.CreateBVH();
|
||||
tree.CreateBVH(model, this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+66
-18
@@ -1239,6 +1239,7 @@ void mjCModel::Clear() {
|
||||
hasImplicitPluginElem = false;
|
||||
compiled = false;
|
||||
errInfo = mjCError();
|
||||
ClearCompileWarnings();
|
||||
qpos0.clear();
|
||||
}
|
||||
|
||||
@@ -1508,7 +1509,21 @@ const mjCError& mjCModel::GetError() const {
|
||||
return errInfo;
|
||||
}
|
||||
|
||||
// add warning to vector (immediate delivery outside compile)
|
||||
void mjCModel::AddWarning(std::string msg, const mjCBase* obj) {
|
||||
if (obj) {
|
||||
msg += "\nElement name '" + obj->name + "', id " + std::to_string(obj->id);
|
||||
if (!obj->info.empty()) {
|
||||
msg += ", " + obj->info;
|
||||
}
|
||||
}
|
||||
|
||||
// outside compile: deliver immediately via normal handler chain
|
||||
if (!compiling_) {
|
||||
mju_warning("%s", msg.c_str());
|
||||
}
|
||||
warnings_.push_back(std::move(msg));
|
||||
}
|
||||
|
||||
// pointer to world body
|
||||
mjCBody* mjCModel::GetWorld() {
|
||||
@@ -3551,8 +3566,10 @@ void mjCModel::CopyObjects(mjModel* m) {
|
||||
if (!pfl->rigid && m->flex_edgeequality[i] == 0 &&
|
||||
!pfl->edgestiffness && !pfl->edgedamping && !pfl->damping &&
|
||||
pfl->bending.empty()) {
|
||||
mju_warning("flex '%s' is not rigid and has no equality constraints "
|
||||
"or passive forces", pfl->name.c_str());
|
||||
AddWarning("flex '" + pfl->name +
|
||||
"' is not rigid and has no equality constraints or "
|
||||
"passive forces",
|
||||
pfl);
|
||||
}
|
||||
|
||||
// copy bvh data (flex aabb computed dynamically in mjData)
|
||||
@@ -4212,9 +4229,10 @@ template void mjCModel::RestoreState<mjtNum>(
|
||||
// resolve keyframe references
|
||||
void mjCModel::StoreKeyframes(mjCModel* dest) {
|
||||
if (this != dest && !key_pending_.empty()) {
|
||||
mju_warning(
|
||||
"Child model has pending keyframes. They will not be namespaced correctly. "
|
||||
"To prevent this, compile the child model before attaching it again.");
|
||||
dest->AddWarning(
|
||||
"Child model has pending keyframes. They will not be namespaced "
|
||||
"correctly. "
|
||||
"To prevent this, compile the child model before attaching it again.");
|
||||
}
|
||||
|
||||
// do not change compilation quantities in case the user wants to recompile preserving the state
|
||||
@@ -4633,10 +4651,17 @@ static void compilerLogHandler(const mjLogMessage* msg) {
|
||||
mju::strcpy_arr(errortext, msg->subject);
|
||||
std::longjmp(error_jmp_buf, 1);
|
||||
} else if (msg->level == mjLOG_WARNING) {
|
||||
// buffer for structured capture (append, not overwrite)
|
||||
if (local_warningtext_ptr) {
|
||||
*local_warningtext_ptr = msg->subject;
|
||||
if (!local_warningtext_ptr->empty()) {
|
||||
*local_warningtext_ptr += '\n';
|
||||
}
|
||||
*local_warningtext_ptr += msg->subject;
|
||||
} else {
|
||||
mju::strcpy_arr(warningtext, msg->subject);
|
||||
if (warningtext[0]) {
|
||||
mju::strcat_arr(warningtext, "\n");
|
||||
}
|
||||
mju::strcat_arr(warningtext, msg->subject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4661,10 +4686,15 @@ mjModel* mjCModel::Compile(const mjVFS* vfs, mjModel** m) {
|
||||
mjModel* volatile model = (m && *m) ? *m : nullptr;
|
||||
mjData* volatile data = nullptr;
|
||||
|
||||
// save log handler
|
||||
mjfLogHandler save_handler = _mjPRIVATE_setTlsLogHandler(compilerLogHandler);
|
||||
// install compiler log handler (captures warnings silently)
|
||||
mjfLogHandler prev_tls = _mjPRIVATE_setTlsLogHandler(compilerLogHandler);
|
||||
|
||||
errInfo = mjCError();
|
||||
|
||||
// set flag so warnings are captured in the spec vector rather than delivered
|
||||
// immediately
|
||||
compiling_ = true;
|
||||
ClearCompileWarnings();
|
||||
warningtext[0] = 0;
|
||||
|
||||
try {
|
||||
@@ -4677,7 +4707,7 @@ mjModel* mjCModel::Compile(const mjVFS* vfs, mjModel** m) {
|
||||
// also include the last warning that was issued. this is useful for
|
||||
// warnings that came out of plugin implementations.
|
||||
if (warningtext[0]) {
|
||||
error_msg += "\n";
|
||||
error_msg += '\n';
|
||||
error_msg += warningtext;
|
||||
}
|
||||
throw mjCError(0, "engine error: %s", error_msg.c_str());
|
||||
@@ -4701,13 +4731,21 @@ mjModel* mjCModel::Compile(const mjVFS* vfs, mjModel** m) {
|
||||
}
|
||||
|
||||
// restore handler, return 0
|
||||
_mjPRIVATE_setTlsLogHandler(save_handler);
|
||||
_mjPRIVATE_setTlsLogHandler(prev_tls);
|
||||
compiling_ = false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// restore log handler, mark as compiled, return mjModel
|
||||
_mjPRIVATE_setTlsLogHandler(save_handler);
|
||||
// restore log handler
|
||||
_mjPRIVATE_setTlsLogHandler(prev_tls);
|
||||
compiling_ = false;
|
||||
compiled = true;
|
||||
|
||||
// play back compile warnings through the normal handler chain
|
||||
for (int i = num_attach_warnings_; i < warnings_.size(); ++i) {
|
||||
mju_warning("%s", warnings_[i].c_str());
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
@@ -5353,7 +5391,22 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
||||
throw mjCError(0, "could not create mjData");
|
||||
}
|
||||
|
||||
// pass compiler warnings into structured warning vector before validation
|
||||
if (warningtext[0]) {
|
||||
std::string warnings(warningtext);
|
||||
std::istringstream stream(warnings);
|
||||
std::string line;
|
||||
while (std::getline(stream, line)) {
|
||||
if (!line.empty()) {
|
||||
AddWarning(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test forward simulation unless asleep_init is true (potentially expensive)
|
||||
// reset warningtext: engine warnings from validation are not compiler
|
||||
// warnings
|
||||
warningtext[0] = 0;
|
||||
if (!asleep_init) {
|
||||
mj_step(m, d);
|
||||
}
|
||||
@@ -5364,11 +5417,6 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
||||
m->opt.enableflags = enableflags;
|
||||
d = nullptr;
|
||||
|
||||
// pass warning back
|
||||
if (warningtext[0]) {
|
||||
mju::strcpy_arr(errInfo.message, warningtext);
|
||||
errInfo.warning = true;
|
||||
}
|
||||
|
||||
// save signature
|
||||
m->signature = Signature();
|
||||
|
||||
+25
-3
@@ -249,6 +249,23 @@ class mjCModel : public mjCModel_, private mjSpec {
|
||||
bool IsCompiled() const; // is model already compiled
|
||||
const mjCError& GetError() const; // get reference of error object
|
||||
void SetError(const mjCError& error) { errInfo = error; } // set value of error object
|
||||
void AddWarning(std::string msg, // add warning to vector
|
||||
const mjCBase* obj = nullptr);
|
||||
const std::vector<std::string>& GetWarnings()
|
||||
const { // get accumulated warnings
|
||||
return warnings_;
|
||||
}
|
||||
void ClearWarnings() {
|
||||
warnings_.clear();
|
||||
num_attach_warnings_ = 0;
|
||||
} // clear all warnings
|
||||
void ClearCompileWarnings() {
|
||||
warnings_.resize(num_attach_warnings_);
|
||||
} // clear compile warnings
|
||||
void SetAttachWarningBoundary() { // snapshot attach warning count
|
||||
num_attach_warnings_ = warnings_.size();
|
||||
}
|
||||
|
||||
mjCBody* GetWorld(); // pointer to world body
|
||||
mjCDef* FindDefault(const std::string& name) const; // find defaults class name
|
||||
mjCDef* AddDefault(std::string name, mjCDef* parent = nullptr); // add defaults class to array
|
||||
@@ -488,10 +505,15 @@ class mjCModel : public mjCModel_, private mjSpec {
|
||||
// expand all keyframes in the model
|
||||
void ExpandAllKeyframes();
|
||||
|
||||
mjListKeyMap ids; // map from object names to ids
|
||||
mjCError errInfo; // last error info
|
||||
mjListKeyMap ids; // map from object names to ids
|
||||
mjCError errInfo; // last error info
|
||||
std::vector<std::string>
|
||||
warnings_; // chronological list of non-fatal warnings
|
||||
int num_attach_warnings_ =
|
||||
0; // boundary: [0, n) are attach, [n, size) are compile
|
||||
bool compiling_ = false; // true during Compile()
|
||||
std::vector<mjKeyInfo> key_pending_; // attached keyframes
|
||||
bool deepcopy_; // copy objects when attaching
|
||||
bool deepcopy_; // copy objects when attaching
|
||||
bool attached_ = false; // true if model is attached to a parent model
|
||||
std::unordered_map<const mjsCompiler*, mjSpec*> compiler2spec_; // map from compiler to spec
|
||||
std::vector<mjCBase*> detached_; // list of detached objects
|
||||
|
||||
+16
-17
@@ -38,12 +38,11 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lodepng.h"
|
||||
#include "cc/array_safety.h"
|
||||
#include "engine/engine_passive.h"
|
||||
#include "engine/engine_support.h"
|
||||
#include "lodepng.h" // NOLINT
|
||||
#include <mujoco/mjspec.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "cc/array_safety.h"
|
||||
#include "engine/engine_passive.h"
|
||||
#include "user/user_api.h"
|
||||
#include "user/user_cache.h"
|
||||
#include "user/user_model.h"
|
||||
@@ -197,7 +196,6 @@ mjCError::mjCError(const mjCBase* obj, const char* msg, const char* str, int pos
|
||||
char temp[600];
|
||||
|
||||
// init
|
||||
warning = false;
|
||||
if (obj || msg) {
|
||||
mju::sprintf_arr(message, "Error");
|
||||
} else {
|
||||
@@ -396,13 +394,13 @@ mjCBoundingVolumeHierarchy::AddBoundingVolume(const int* id, int contype, int co
|
||||
|
||||
|
||||
// create bounding volume hierarchy
|
||||
void mjCBoundingVolumeHierarchy::CreateBVH() {
|
||||
void mjCBoundingVolumeHierarchy::CreateBVH(mjCModel* model,
|
||||
const mjCBase* owner) {
|
||||
std::vector<BVElement> elements;
|
||||
Make(elements);
|
||||
MakeBVH(elements.begin(), elements.end());
|
||||
MakeBVH(elements.begin(), elements.end(), 0, model, owner);
|
||||
}
|
||||
|
||||
|
||||
void mjCBoundingVolumeHierarchy::Make(std::vector<BVElement>& elements) {
|
||||
// precompute the positions of each element in the hierarchy's axes, and drop
|
||||
// visual-only elements.
|
||||
@@ -424,8 +422,9 @@ void mjCBoundingVolumeHierarchy::Make(std::vector<BVElement>& elements) {
|
||||
|
||||
// compute bounding volume hierarchy
|
||||
int mjCBoundingVolumeHierarchy::MakeBVH(
|
||||
std::vector<BVElement>::iterator elements_begin,
|
||||
std::vector<BVElement>::iterator elements_end, int lev) {
|
||||
std::vector<BVElement>::iterator elements_begin,
|
||||
std::vector<BVElement>::iterator elements_end, int lev, mjCModel* model,
|
||||
const mjCBase* owner) {
|
||||
int nelements = elements_end - elements_begin;
|
||||
if (nelements == 0) {
|
||||
return -1;
|
||||
@@ -525,11 +524,13 @@ int mjCBoundingVolumeHierarchy::MakeBVH(
|
||||
|
||||
// recursive calls
|
||||
if (m > 0) {
|
||||
child_[2*index + 0] = MakeBVH(elements_begin, elements_begin + m, lev + 1);
|
||||
child_[2 * index + 0] =
|
||||
MakeBVH(elements_begin, elements_begin + m, lev + 1, model, owner);
|
||||
}
|
||||
|
||||
if (m != nelements) {
|
||||
child_[2*index + 1] = MakeBVH(elements_begin + m, elements_end, lev + 1);
|
||||
child_[2 * index + 1] =
|
||||
MakeBVH(elements_begin + m, elements_end, lev + 1, model, owner);
|
||||
}
|
||||
|
||||
// SHOULD NOT OCCUR
|
||||
@@ -539,14 +540,12 @@ int mjCBoundingVolumeHierarchy::MakeBVH(
|
||||
}
|
||||
|
||||
if (lev > mjMAXTREEDEPTH) {
|
||||
mju_warning("max tree depth exceeded in body=%s", name_.c_str());
|
||||
model->AddWarning("max tree depth exceeded", owner);
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------------------- class mjCOctree implementation --------------------------------------------
|
||||
|
||||
void mjCOctree::CopyLevel(int* level) const {
|
||||
@@ -2631,7 +2630,7 @@ void mjCBody::ComputeBVH() {
|
||||
tree.AddBoundingVolume(&geom->id, geom->contype, geom->conaffinity,
|
||||
geom->pos, geom->quat, geom->aabb);
|
||||
}
|
||||
tree.CreateBVH();
|
||||
tree.CreateBVH(model, this);
|
||||
}
|
||||
|
||||
|
||||
@@ -3871,7 +3870,7 @@ void mjCGeom::SetFluidCoefs(void) {
|
||||
|
||||
// compute bounding box
|
||||
void mjCGeom::ComputeAABB(void) {
|
||||
double aamm[6]; // axis-aligned bounding box in (min, max) format
|
||||
double aamm[6]; // axis-aligned bounding box in (min, max) format
|
||||
switch (type) {
|
||||
case mjGEOM_HFIELD:
|
||||
aamm[0] = -hfield->size[0];
|
||||
|
||||
@@ -83,7 +83,6 @@ class [[nodiscard]] mjCError {
|
||||
int pos2 = 0);
|
||||
|
||||
char message[500]; // error message
|
||||
bool warning; // is this a warning instead of error
|
||||
};
|
||||
|
||||
// alternative specifications of frame orientation
|
||||
@@ -172,7 +171,7 @@ struct mjCBoundingVolumeHierarchy_ {
|
||||
class mjCBoundingVolumeHierarchy : public mjCBoundingVolumeHierarchy_ {
|
||||
public:
|
||||
// make bounding volume hierarchy
|
||||
void CreateBVH();
|
||||
void CreateBVH(mjCModel* model, const mjCBase* owner);
|
||||
void Set(double ipos_element[3], double iquat_element[4]);
|
||||
void AllocateBoundingVolumes(int nleaf);
|
||||
void RemoveInactiveVolumes(int nmax);
|
||||
@@ -210,7 +209,8 @@ class mjCBoundingVolumeHierarchy : public mjCBoundingVolumeHierarchy_ {
|
||||
};
|
||||
void Make(std::vector<BVElement>& elements);
|
||||
int MakeBVH(std::vector<BVElement>::iterator elements_begin,
|
||||
std::vector<BVElement>::iterator elements_end, int lev = 0);
|
||||
std::vector<BVElement>::iterator elements_end, int lev,
|
||||
mjCModel* model, const mjCBase* owner);
|
||||
};
|
||||
|
||||
|
||||
|
||||
+10
-2
@@ -58,8 +58,16 @@ mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
|
||||
}
|
||||
|
||||
// handle compile warning
|
||||
if (mjs_isWarning(spec.get())) {
|
||||
mjCopyError(error, mjs_getError(spec.get()), error_sz);
|
||||
int num_warnings = mjs_numWarnings(spec.get());
|
||||
if (num_warnings > 0) {
|
||||
std::string all_warnings;
|
||||
for (int i = 0; i < num_warnings; ++i) {
|
||||
if (!all_warnings.empty()) {
|
||||
all_warnings += '\n';
|
||||
}
|
||||
all_warnings += mjs_getWarning(spec.get(), i);
|
||||
}
|
||||
mjCopyError(error, all_warnings.c_str(), error_sz);
|
||||
} else if (error) {
|
||||
error[0] = '\0';
|
||||
}
|
||||
|
||||
@@ -80,7 +80,9 @@ struct ActLimitedTestCase {
|
||||
mjtIntegrator integrator;
|
||||
};
|
||||
|
||||
using ParametrizedForwardTest = ::testing::TestWithParam<ActLimitedTestCase>;
|
||||
class ParametrizedForwardTest
|
||||
: public MujocoTest,
|
||||
public ::testing::WithParamInterface<ActLimitedTestCase> {};
|
||||
|
||||
TEST_P(ParametrizedForwardTest, ActLimited) {
|
||||
static constexpr char xml[] = R"(
|
||||
|
||||
@@ -1753,6 +1753,8 @@ TEST_F(SensorTest, InsideSiteFlexBody) {
|
||||
)";
|
||||
|
||||
char error[1024] = {0};
|
||||
EXPECT_CALL(mock_warning_handler, Warn(testing::HasSubstr("is not rigid")))
|
||||
.WillOnce(testing::Return());
|
||||
mjModel* m = LoadModelFromString(xml, error, sizeof(error));
|
||||
ASSERT_THAT(m, NotNull()) << error;
|
||||
mjData* d = mj_makeData(m);
|
||||
@@ -1839,6 +1841,8 @@ TEST_F(SensorTest, FlexContactSensors) {
|
||||
)";
|
||||
|
||||
char error[1024] = {0};
|
||||
EXPECT_CALL(mock_warning_handler, Warn(testing::HasSubstr("is not rigid")))
|
||||
.WillOnce(testing::Return());
|
||||
mjModel* m = LoadModelFromString(xml, error, sizeof(error));
|
||||
ASSERT_THAT(m, NotNull()) << error;
|
||||
mjData* d = mj_makeData(m);
|
||||
|
||||
@@ -45,7 +45,9 @@ constexpr int GetExpectedStackUsageBytes() {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestMjArrayList, TestMjArrayListSingleThreaded) {
|
||||
class TestMjArrayList : public MujocoTest {};
|
||||
|
||||
TEST_F(TestMjArrayList, TestMjArrayListSingleThreaded) {
|
||||
std::array<char, 1024> error;
|
||||
mjModel* m = LoadModelFromString("<mujoco/>", error.data(), error.size());
|
||||
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error.data();
|
||||
@@ -81,7 +83,7 @@ TEST(TestMjArrayList, TestMjArrayListSingleThreaded) {
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
|
||||
TEST(TestMjArrayList, ZeroInitialCapacity) {
|
||||
TEST_F(TestMjArrayList, ZeroInitialCapacity) {
|
||||
char error[1024];
|
||||
mjModel* m = LoadModelFromString("<mujoco/>", error, sizeof(error));
|
||||
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error;
|
||||
|
||||
+99
-13
@@ -24,7 +24,6 @@
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -33,6 +32,7 @@
|
||||
#include <absl/base/attributes.h>
|
||||
#include <absl/base/const_init.h>
|
||||
#include <absl/base/thread_annotations.h>
|
||||
#include <absl/strings/match.h>
|
||||
#include <absl/strings/str_cat.h>
|
||||
#include <absl/strings/str_join.h>
|
||||
#include <absl/synchronization/mutex.h>
|
||||
@@ -41,35 +41,103 @@
|
||||
#include "src/xml/xml_global.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
using ::testing::_;
|
||||
using ::testing::Not;
|
||||
using ::testing::Return;
|
||||
using ::testing::Truly;
|
||||
|
||||
// Returns true if the warning matches a known benign warning to ignore.
|
||||
bool IsBenignWarning(const std::string& msg) {
|
||||
static const char* const kBenignWarnings[] = {
|
||||
"is not rigid and has no equality constraints",
|
||||
};
|
||||
for (const char* warning : kBenignWarnings) {
|
||||
if (absl::StrContains(msg, warning)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
thread_local MockWarningHandler* MockWarningHandler::active_handler = nullptr;
|
||||
|
||||
// Registers this handler as the active one.
|
||||
MockWarningHandler::MockWarningHandler() {
|
||||
prev_ = active_handler;
|
||||
active_handler = this;
|
||||
|
||||
// By default, ignore matches in the benign warnings list
|
||||
ON_CALL(*this, Warn(Truly(IsBenignWarning)))
|
||||
.WillByDefault([](const std::string&) {});
|
||||
|
||||
// Fail on all other warnings
|
||||
ON_CALL(*this, Warn(Not(Truly(IsBenignWarning))))
|
||||
.WillByDefault([](const std::string& msg) {
|
||||
ADD_FAILURE() << "mju_user_warning: " << msg;
|
||||
});
|
||||
}
|
||||
|
||||
// Restores the previously active warning handler.
|
||||
MockWarningHandler::~MockWarningHandler() { active_handler = prev_; }
|
||||
|
||||
// Configures the mock warning handler to ignore all warnings.
|
||||
void MockWarningHandler::ExpectWarnings() {
|
||||
EXPECT_CALL(*this, Warn(_)).WillRepeatedly(Return());
|
||||
}
|
||||
|
||||
// Returns the active warning handler.
|
||||
MockWarningHandler* MockWarningHandler::GetActive() { return active_handler; }
|
||||
|
||||
namespace {
|
||||
|
||||
using ::testing::NotNull;
|
||||
|
||||
ABSL_CONST_INIT static absl::Mutex handlers_mutex(absl::kConstInit);
|
||||
static int guard_count ABSL_GUARDED_BY(handlers_mutex) = 0;
|
||||
static mjfLogHandler prev_log_handler ABSL_GUARDED_BY(handlers_mutex) = nullptr;
|
||||
|
||||
void default_mj_error_handler(const char* msg) {
|
||||
FAIL() << "mju_user_error: " << msg;
|
||||
}
|
||||
void default_mj_log_handler(const mjLogMessage* msg) {
|
||||
std::string subject = msg->subject;
|
||||
if (msg->func) {
|
||||
subject = std::string(msg->func) + ": " + msg->subject;
|
||||
}
|
||||
|
||||
void default_mj_warning_handler(const char* msg) {
|
||||
ADD_FAILURE() << "mju_user_warning: " << msg;
|
||||
if (msg->level == mjLOG_ERROR) {
|
||||
if (mju_user_error) {
|
||||
mju_user_error(subject.c_str());
|
||||
} else {
|
||||
FAIL() << "mju_user_error: " << subject;
|
||||
}
|
||||
} else if (msg->level == mjLOG_WARNING) {
|
||||
std::string full_msg = subject;
|
||||
if (msg->body) {
|
||||
full_msg += "\n" + std::string(msg->body);
|
||||
}
|
||||
if (mju_user_warning) {
|
||||
mju_user_warning(full_msg.c_str());
|
||||
} else if (auto* handler = MockWarningHandler::GetActive()) {
|
||||
handler->Warn(full_msg);
|
||||
} else {
|
||||
ADD_FAILURE() << "mju_user_warning: " << full_msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
MujocoErrorTestGuard::MujocoErrorTestGuard() {
|
||||
absl::MutexLock lock(handlers_mutex);
|
||||
if (++guard_count == 1) {
|
||||
mju_user_error = default_mj_error_handler;
|
||||
mju_user_warning = default_mj_warning_handler;
|
||||
prev_log_handler = mju_setLogHandler(default_mj_log_handler);
|
||||
}
|
||||
}
|
||||
|
||||
MujocoErrorTestGuard::~MujocoErrorTestGuard() {
|
||||
absl::MutexLock lock(handlers_mutex);
|
||||
if (--guard_count == 0) {
|
||||
mju_user_error = nullptr;
|
||||
mju_user_warning = nullptr;
|
||||
mju_setLogHandler(prev_log_handler);
|
||||
prev_log_handler = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,11 +165,29 @@ mjModel* LoadModelFromString(std::string_view xml, char* error,
|
||||
if (spec) {
|
||||
model = mj_compile(spec, vfs);
|
||||
|
||||
if (error && (!model || mjs_isWarning(spec))) {
|
||||
strncpy(error, mjs_getError(spec), error_size);
|
||||
error[error_size - 1] = '\0';
|
||||
if (error) {
|
||||
if (!model) {
|
||||
strncpy(error, mjs_getError(spec), error_size);
|
||||
error[error_size - 1] = '\0';
|
||||
} else {
|
||||
int num_warnings = mjs_numWarnings(spec);
|
||||
if (num_warnings > 0) {
|
||||
std::string all_warnings;
|
||||
for (int i = 0; i < num_warnings; ++i) {
|
||||
if (!all_warnings.empty()) {
|
||||
all_warnings += '\n';
|
||||
}
|
||||
all_warnings += mjs_getWarning(spec, i);
|
||||
}
|
||||
strncpy(error, all_warnings.c_str(), error_size);
|
||||
error[error_size - 1] = '\0';
|
||||
} else {
|
||||
error[0] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetGlobalXmlSpec(spec);
|
||||
return model;
|
||||
}
|
||||
|
||||
+26
-1
@@ -44,7 +44,7 @@ namespace mujoco {
|
||||
inline mjtNum MjTolScale() {
|
||||
static const mjtNum scale = []() {
|
||||
const char* env = std::getenv("MJTOL_SCALE");
|
||||
return env ? std::atof(env) : 1.0;
|
||||
return env ? std::strtod(env, nullptr) : 1.0;
|
||||
}();
|
||||
return scale;
|
||||
}
|
||||
@@ -106,6 +106,28 @@ class MujocoErrorTestGuard {
|
||||
~MujocoErrorTestGuard();
|
||||
};
|
||||
|
||||
// Mock handler for capturing and verifying mju_warning logs.
|
||||
class MockWarningHandler {
|
||||
public:
|
||||
// Constructor that registers this handler as the active one.
|
||||
MockWarningHandler();
|
||||
// Destructor that restores the previously active handler.
|
||||
~MockWarningHandler();
|
||||
|
||||
// Mock method called when a warning is intercepted.
|
||||
MOCK_METHOD(void, Warn, (const std::string& msg));
|
||||
|
||||
// Allow any number of warnings without triggering test failure.
|
||||
void ExpectWarnings();
|
||||
|
||||
// Returns the thread-local active mock warning handler.
|
||||
static MockWarningHandler* GetActive();
|
||||
|
||||
private:
|
||||
static thread_local MockWarningHandler* active_handler;
|
||||
MockWarningHandler* prev_ = nullptr;
|
||||
};
|
||||
|
||||
// A test fixture which simplifies writing tests for the MuJoCo C API.
|
||||
// By default, any MuJoCo operation which triggers a warning or error will
|
||||
// trigger a test failure.
|
||||
@@ -128,6 +150,9 @@ class MujocoTest : public ::testing::Test {
|
||||
}
|
||||
~MujocoTest() { mj_freeLastXML(); }
|
||||
|
||||
protected:
|
||||
MockWarningHandler mock_warning_handler;
|
||||
|
||||
private:
|
||||
MujocoErrorTestGuard error_guard;
|
||||
};
|
||||
|
||||
@@ -34,6 +34,13 @@ TEST_F(MujocoTestTest, MjUserWarningFailsTest) {
|
||||
EXPECT_NONFATAL_FAILURE(mju_warning("Warning."), "Warning.");
|
||||
}
|
||||
|
||||
TEST_F(MujocoTestTest, BenignWarningDoesNotFailTest) {
|
||||
// Warnings in the benign list should not trigger test failures
|
||||
mju_warning(
|
||||
"flex 'soft' is not rigid and has no equality constraints "
|
||||
"or passive forces");
|
||||
}
|
||||
|
||||
TEST_F(MujocoTestTest, MjUserErrorFailsTest) {
|
||||
EXPECT_FATAL_FAILURE(mju_error("Error."), "Error.");
|
||||
}
|
||||
|
||||
@@ -3291,5 +3291,168 @@ TEST_F(MujocoTest, CompilerTimers) {
|
||||
mj_deleteSpec(spec);
|
||||
}
|
||||
|
||||
// -------------------- test compile warning infrastructure --------------------
|
||||
|
||||
TEST_F(MujocoTest, CompileWarningCount) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="parent">
|
||||
<geom size="1"/>
|
||||
<flexcomp name="grid" type="grid" count="3 3 1" spacing="0.1 0.1 0.1"
|
||||
dim="2" radius="0.01">
|
||||
<contact internal="false"/>
|
||||
</flexcomp>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size());
|
||||
ASSERT_THAT(spec, NotNull()) << error.data();
|
||||
|
||||
mjModel* model = mj_compile(spec, 0);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
|
||||
// flex with no passive forces should produce a warning
|
||||
EXPECT_GT(mjs_numWarnings(spec), 0);
|
||||
EXPECT_THAT(mjs_getWarning(spec, 0), HasSubstr("not rigid"));
|
||||
|
||||
mj_deleteModel(model);
|
||||
mj_deleteSpec(spec);
|
||||
}
|
||||
|
||||
TEST_F(MujocoTest, CompileWarningOutOfBounds) {
|
||||
mjSpec* spec = mj_makeSpec();
|
||||
mjsBody* world = mjs_findBody(spec, "world");
|
||||
mjsGeom* geom = mjs_addGeom(world, 0);
|
||||
geom->size[0] = 1;
|
||||
|
||||
mjModel* model = mj_compile(spec, 0);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
|
||||
// no warnings expected for simple model
|
||||
EXPECT_EQ(mjs_numWarnings(spec), 0);
|
||||
EXPECT_THAT(mjs_getWarning(spec, 0), IsNull());
|
||||
EXPECT_THAT(mjs_getWarning(spec, -1), IsNull());
|
||||
|
||||
// nullptr spec should not crash
|
||||
EXPECT_EQ(mjs_numWarnings(nullptr), 0);
|
||||
EXPECT_THAT(mjs_getWarning(nullptr, 0), IsNull());
|
||||
|
||||
mj_deleteModel(model);
|
||||
mj_deleteSpec(spec);
|
||||
}
|
||||
|
||||
TEST_F(MujocoTest, RecompileClearsCompileWarnings) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="parent">
|
||||
<geom size="1"/>
|
||||
<flexcomp name="grid" type="grid" count="3 3 1" spacing="0.1 0.1 0.1"
|
||||
dim="2" radius="0.01">
|
||||
<contact internal="false"/>
|
||||
</flexcomp>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size());
|
||||
ASSERT_THAT(spec, NotNull()) << error.data();
|
||||
|
||||
mjModel* model = mj_compile(spec, 0);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
int first_count = mjs_numWarnings(spec);
|
||||
EXPECT_GT(first_count, 0);
|
||||
|
||||
// recompile — warnings should be regenerated, not accumulated
|
||||
mj_deleteModel(model);
|
||||
model = mj_compile(spec, 0);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
EXPECT_EQ(mjs_numWarnings(spec), first_count);
|
||||
|
||||
mj_deleteModel(model);
|
||||
mj_deleteSpec(spec);
|
||||
}
|
||||
|
||||
TEST_F(MujocoTest, LoadXMLWarningInErrorBuffer) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="parent">
|
||||
<geom size="1"/>
|
||||
<flexcomp name="grid" type="grid" count="3 3 1" spacing="0.1 0.1 0.1"
|
||||
dim="2" radius="0.01">
|
||||
<contact internal="false"/>
|
||||
</flexcomp>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
// write xml to VFS
|
||||
mjVFS vfs;
|
||||
mj_defaultVFS(&vfs);
|
||||
mj_addBufferVFS(&vfs, "model.xml", xml, sizeof(xml));
|
||||
|
||||
std::array<char, 1024> error;
|
||||
error[0] = '\0';
|
||||
mjModel* model = mj_loadXML("model.xml", &vfs, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull());
|
||||
|
||||
// warning should be in the error buffer
|
||||
EXPECT_THAT(error.data(), HasSubstr("not rigid"));
|
||||
|
||||
mj_deleteModel(model);
|
||||
mj_deleteVFS(&vfs);
|
||||
}
|
||||
|
||||
TEST_F(MujocoTest, CompileWarningChainedToHandler) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="parent">
|
||||
<geom size="1"/>
|
||||
<flexcomp name="grid" type="grid" count="3 3 1" spacing="0.1 0.1 0.1"
|
||||
dim="2" radius="0.01">
|
||||
<contact internal="false"/>
|
||||
</flexcomp>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size());
|
||||
ASSERT_THAT(spec, NotNull()) << error.data();
|
||||
|
||||
// install a custom log handler that captures warnings
|
||||
std::vector<std::string> captured_warnings;
|
||||
static thread_local std::vector<std::string>* capture_ptr = nullptr;
|
||||
capture_ptr = &captured_warnings;
|
||||
|
||||
// install custom log handler (replaces global, so mock is bypassed)
|
||||
|
||||
mjfLogHandler prev = mju_setLogHandler([](const mjLogMessage* msg) {
|
||||
if (msg->level == mjLOG_WARNING && capture_ptr) {
|
||||
capture_ptr->push_back(msg->subject);
|
||||
}
|
||||
});
|
||||
|
||||
mjModel* model = mj_compile(spec, 0);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
|
||||
// restore log handler
|
||||
mju_setLogHandler(prev);
|
||||
capture_ptr = nullptr;
|
||||
|
||||
// chaining should have forwarded warnings to our handler
|
||||
EXPECT_THAT(captured_warnings, testing::Contains(HasSubstr("not rigid")));
|
||||
|
||||
mj_deleteModel(model);
|
||||
mj_deleteSpec(spec);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
|
||||
@@ -1181,7 +1181,7 @@ TEST_F(MjCGeomTest, IgnoreBadGeomOutsideInertiagrouprange) {
|
||||
TEST_F(MjCGeomTest, NanSize) {
|
||||
// even if the caller ignores warnings, models shouldn't compile with NaN
|
||||
// geom sizes
|
||||
mju_user_warning = nullptr;
|
||||
mock_warning_handler.ExpectWarnings();
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
|
||||
@@ -84,6 +84,12 @@ class WriteReadCompareTest : public XMLWriterTest,
|
||||
TEST_P(WriteReadCompareTest, WriteReadCompare) {
|
||||
std::string xml = GetParam();
|
||||
|
||||
// If this is the flex_line_obj model, expect the 'is not rigid' warning
|
||||
if (absl::StrContains(xml, "flex_line_obj")) {
|
||||
EXPECT_CALL(mock_warning_handler, Warn(testing::HasSubstr("is not rigid")))
|
||||
.WillRepeatedly(testing::Return());
|
||||
}
|
||||
|
||||
// full precision float printing
|
||||
FullFloatPrecision increase_precision;
|
||||
|
||||
|
||||
@@ -115,15 +115,20 @@ EMSCRIPTEN_DECLARE_VAL_TYPE(StringOrNull);
|
||||
mju_error("Invalid argument: %s is undefined", #val); \
|
||||
}
|
||||
|
||||
void ThrowMujocoErrorToJS(const char* msg) {
|
||||
// Get a handle to the JS global Error constructor function, create a new
|
||||
// object instance and then throw the object as an exception using the
|
||||
// val::throw_() helper function.
|
||||
val(val::global("Error").new_(val("MuJoCo Error: " + std::string(msg))))
|
||||
.throw_();
|
||||
void ThrowMujocoErrorToJS(const mjLogMessage* msg) {
|
||||
if (msg->level == mjLOG_ERROR) {
|
||||
std::string message = msg->subject;
|
||||
if (msg->func) {
|
||||
message = std::string(msg->func) + ": " + msg->subject;
|
||||
}
|
||||
// Get a handle to the JS global Error constructor function, create a new
|
||||
// object instance and then throw the object as an exception using the
|
||||
// val::throw_() helper function.
|
||||
val(val::global("Error").new_(val("MuJoCo Error: " + message))).throw_();
|
||||
}
|
||||
}
|
||||
__attribute__((constructor)) void InitMuJoCoErrorHandler() {
|
||||
mju_user_error = ThrowMujocoErrorToJS;
|
||||
mju_setLogHandler(ThrowMujocoErrorToJS);
|
||||
}
|
||||
|
||||
// Generates a descriptive error message for when a key lookup fails.
|
||||
@@ -8730,20 +8735,44 @@ std::unique_ptr<MjSpec> parseXMLString_wrapper(const std::string &xml) {
|
||||
|
||||
std::unique_ptr<MjModel> mj_compile_wrapper_1(const MjSpec& spec) {
|
||||
mjSpec* spec_ptr = spec.get();
|
||||
|
||||
// suppress stderr playback: warnings are raised via console.warn() below
|
||||
mjfLogHandler prev = _mjPRIVATE_setTlsLogHandler([](const mjLogMessage*) {});
|
||||
mjModel* model = mj_compile(spec_ptr, nullptr);
|
||||
if (!model || mjs_isWarning(spec_ptr)) {
|
||||
_mjPRIVATE_setTlsLogHandler(prev);
|
||||
if (!model) {
|
||||
mju_error("%s", mjs_getError(spec_ptr));
|
||||
}
|
||||
int num_warnings = mjs_numWarnings(spec_ptr);
|
||||
if (num_warnings > 0) {
|
||||
for (int i = 0; i < num_warnings; ++i) {
|
||||
val::global("console").call<void>(
|
||||
"warn",
|
||||
val("MuJoCo Warning: " + std::string(mjs_getWarning(spec_ptr, i))));
|
||||
}
|
||||
}
|
||||
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||
}
|
||||
|
||||
std::unique_ptr<MjModel> mj_compile_wrapper_2(const MjSpec& spec, const MjVFS& vfs) {
|
||||
mjSpec* spec_ptr = spec.get();
|
||||
mjVFS* vfs_ptr = vfs.get();
|
||||
|
||||
// suppress stderr playback: warnings are raised via console.warn() below
|
||||
mjfLogHandler prev = _mjPRIVATE_setTlsLogHandler([](const mjLogMessage*) {});
|
||||
mjModel* model = mj_compile(spec_ptr, vfs_ptr);
|
||||
if (!model || mjs_isWarning(spec_ptr)) {
|
||||
_mjPRIVATE_setTlsLogHandler(prev);
|
||||
if (!model) {
|
||||
mju_error("%s", mjs_getError(spec_ptr));
|
||||
}
|
||||
int num_warnings = mjs_numWarnings(spec_ptr);
|
||||
if (num_warnings > 0) {
|
||||
for (int i = 0; i < num_warnings; ++i) {
|
||||
val::global("console").call<void>(
|
||||
"warn",
|
||||
val("MuJoCo Warning: " + std::string(mjs_getWarning(spec_ptr, i))));
|
||||
}
|
||||
}
|
||||
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||
}
|
||||
|
||||
@@ -10101,6 +10130,10 @@ std::optional<MjsDefault> mjs_getSpecDefault_wrapper(const MjSpec& s) {
|
||||
return MjsDefault(result);
|
||||
}
|
||||
|
||||
std::string mjs_getWarning_wrapper(const MjSpec& spec, int index) {
|
||||
return std::string(mjs_getWarning(spec.get(), index));
|
||||
}
|
||||
|
||||
std::optional<MjsWrap> mjs_getWrap_wrapper(const MjsTendon& tendonspec, int i) {
|
||||
mjsWrap* result = mjs_getWrap(tendonspec.get(), i);
|
||||
if (result == nullptr) {
|
||||
@@ -10178,6 +10211,10 @@ std::optional<MjsElement> mjs_nextElement_wrapper(const MjSpec& s, const MjsElem
|
||||
return MjsElement(result);
|
||||
}
|
||||
|
||||
int mjs_numWarnings_wrapper(const MjSpec& spec) {
|
||||
return mjs_numWarnings(spec.get());
|
||||
}
|
||||
|
||||
std::string mjs_resolveOrientation_wrapper(const val& quat, mjtByte degree, const String& sequence, const MjsOrientation& orientation) {
|
||||
CHECK_VAL(sequence);
|
||||
UNPACK_VALUE(double, quat);
|
||||
@@ -13742,6 +13779,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
function("mjs_getParent", &mjs_getParent_wrapper);
|
||||
function("mjs_getSpec", &mjs_getSpec_wrapper);
|
||||
function("mjs_getSpecDefault", &mjs_getSpecDefault_wrapper);
|
||||
function("mjs_getWarning", &mjs_getWarning_wrapper);
|
||||
function("mjs_getWrap", &mjs_getWrap_wrapper);
|
||||
function("mjs_getWrapCoef", &mjs_getWrapCoef_wrapper);
|
||||
function("mjs_getWrapDivisor", &mjs_getWrapDivisor_wrapper);
|
||||
@@ -13753,6 +13791,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
function("mjs_makeMesh", &mjs_makeMesh_wrapper);
|
||||
function("mjs_nextChild", &mjs_nextChild_wrapper);
|
||||
function("mjs_nextElement", &mjs_nextElement_wrapper);
|
||||
function("mjs_numWarnings", &mjs_numWarnings_wrapper);
|
||||
function("mjs_resolveOrientation", &mjs_resolveOrientation_wrapper);
|
||||
function("mjs_sensorDim", &mjs_sensorDim_wrapper);
|
||||
function("mjs_setDeepCopy", &mjs_setDeepCopy_wrapper);
|
||||
|
||||
@@ -113,15 +113,20 @@ EMSCRIPTEN_DECLARE_VAL_TYPE(StringOrNull);
|
||||
mju_error("Invalid argument: %s is undefined", #val); \
|
||||
}
|
||||
|
||||
void ThrowMujocoErrorToJS(const char* msg) {
|
||||
// Get a handle to the JS global Error constructor function, create a new
|
||||
// object instance and then throw the object as an exception using the
|
||||
// val::throw_() helper function.
|
||||
val(val::global("Error").new_(val("MuJoCo Error: " + std::string(msg))))
|
||||
.throw_();
|
||||
void ThrowMujocoErrorToJS(const mjLogMessage* msg) {
|
||||
if (msg->level == mjLOG_ERROR) {
|
||||
std::string message = msg->subject;
|
||||
if (msg->func) {
|
||||
message = std::string(msg->func) + ": " + msg->subject;
|
||||
}
|
||||
// Get a handle to the JS global Error constructor function, create a new
|
||||
// object instance and then throw the object as an exception using the
|
||||
// val::throw_() helper function.
|
||||
val(val::global("Error").new_(val("MuJoCo Error: " + message))).throw_();
|
||||
}
|
||||
}
|
||||
__attribute__((constructor)) void InitMuJoCoErrorHandler() {
|
||||
mju_user_error = ThrowMujocoErrorToJS;
|
||||
mju_setLogHandler(ThrowMujocoErrorToJS);
|
||||
}
|
||||
|
||||
// Generates a descriptive error message for when a key lookup fails.
|
||||
@@ -798,20 +803,44 @@ std::unique_ptr<MjSpec> parseXMLString_wrapper(const std::string &xml) {
|
||||
|
||||
std::unique_ptr<MjModel> mj_compile_wrapper_1(const MjSpec& spec) {
|
||||
mjSpec* spec_ptr = spec.get();
|
||||
|
||||
// suppress stderr playback: warnings are raised via console.warn() below
|
||||
mjfLogHandler prev = _mjPRIVATE_setTlsLogHandler([](const mjLogMessage*) {});
|
||||
mjModel* model = mj_compile(spec_ptr, nullptr);
|
||||
if (!model || mjs_isWarning(spec_ptr)) {
|
||||
_mjPRIVATE_setTlsLogHandler(prev);
|
||||
if (!model) {
|
||||
mju_error("%s", mjs_getError(spec_ptr));
|
||||
}
|
||||
int num_warnings = mjs_numWarnings(spec_ptr);
|
||||
if (num_warnings > 0) {
|
||||
for (int i = 0; i < num_warnings; ++i) {
|
||||
val::global("console").call<void>(
|
||||
"warn",
|
||||
val("MuJoCo Warning: " + std::string(mjs_getWarning(spec_ptr, i))));
|
||||
}
|
||||
}
|
||||
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||
}
|
||||
|
||||
std::unique_ptr<MjModel> mj_compile_wrapper_2(const MjSpec& spec, const MjVFS& vfs) {
|
||||
mjSpec* spec_ptr = spec.get();
|
||||
mjVFS* vfs_ptr = vfs.get();
|
||||
|
||||
// suppress stderr playback: warnings are raised via console.warn() below
|
||||
mjfLogHandler prev = _mjPRIVATE_setTlsLogHandler([](const mjLogMessage*) {});
|
||||
mjModel* model = mj_compile(spec_ptr, vfs_ptr);
|
||||
if (!model || mjs_isWarning(spec_ptr)) {
|
||||
_mjPRIVATE_setTlsLogHandler(prev);
|
||||
if (!model) {
|
||||
mju_error("%s", mjs_getError(spec_ptr));
|
||||
}
|
||||
int num_warnings = mjs_numWarnings(spec_ptr);
|
||||
if (num_warnings > 0) {
|
||||
for (int i = 0; i < num_warnings; ++i) {
|
||||
val::global("console").call<void>(
|
||||
"warn",
|
||||
val("MuJoCo Warning: " + std::string(mjs_getWarning(spec_ptr, i))));
|
||||
}
|
||||
}
|
||||
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user