From ebd4abae03114e1b6fbc650e0d0b459ad6460bbb Mon Sep 17 00:00:00 2001 From: Sam Haves Date: Fri, 17 Jul 2026 12:02:03 -0700 Subject: [PATCH] Add model.xml fallback search path to mjz decoder PiperOrigin-RevId: 949690037 Change-Id: I93978c1f9ded12c69ffe321fe1f85721db32f555 --- doc/changelog.rst | 1 + src/xml/mjz/mjz_decoder.cc | 33 +++++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 94b1e7e8..88f00fcf 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -72,6 +72,7 @@ General integrator and flex stiffness present; Newton and PGS are unaffected. Bending-only models pay zero per-step factorization cost (the factor is precomputed in :ref:`mj_setConst`). Inverse dynamics (:ref:`mj_inverse`) is now discrete-consistent with forward dynamics for gated models. +- The ``mjz`` decoder now searches for ``model.xml`` at the root of the archive as a fallback if the archive-named XML is not found. .. admonition:: Breaking API changes :class: attention diff --git a/src/xml/mjz/mjz_decoder.cc b/src/xml/mjz/mjz_decoder.cc index 20c83dc7..eaf6c7c4 100644 --- a/src/xml/mjz/mjz_decoder.cc +++ b/src/xml/mjz/mjz_decoder.cc @@ -87,20 +87,33 @@ class ZipArchiveProvider : public mjpResourceProvider { files_[stat.m_filename] = FileInfo{i, size, {}}; } - // Look for the root XML model in the archive. First look for an XML file - // with the same name as the archive itself. Failing that, look for an XML - // file within a subdirectory with the same name as the archive. + // Look for the root XML model in the archive. We try the following + // locations: + // 1. [archive_name].xml at the root of the archive. + // 2. [archive_name]/[archive_name].xml inside a subdirectory. + // 3. model.xml at the root of the archive (common zipped MJCF pattern). const std::filesystem::path path(name_); - root_model_ = (path / path.stem()).generic_string() + ".xml"; - if (!Contains(root_model_)) { - root_model_ = - (path / path.stem() / path.stem()).generic_string() + ".xml"; - if (!Contains(root_model_)) { - SetError(error, error_sz, "Zip error: no root XML file found."); - return; + const std::string stem = path.stem().string(); + std::vector candidates = { + (path / stem).generic_string() + ".xml", + (path / stem / stem).generic_string() + ".xml", + (path / "model").generic_string() + ".xml", + }; + + bool found = false; + for (const auto& candidate : candidates) { + if (Contains(candidate)) { + root_model_ = candidate; + found = true; + break; } } + if (!found) { + SetError(error, error_sz, "Zip error: no root XML file found."); + return; + } + // Setup mjpResourceProvider callbacks. mount = [](mjResource* resource) { return 0;