Change compile sample to use mj_parse and mj_encode as input/output.
There is a single remaining exception for mjb as it's a special case by bypassing mjSpec. PiperOrigin-RevId: 943556676 Change-Id: Iaf480ea6d2d42a6007b35c23435b25c9eb24ead0
This commit is contained in:
committed by
Copybara-Service
parent
19e175c063
commit
ae58855c15
+81
-111
@@ -12,42 +12,37 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#include <mujoco/mujoco.h>
|
#include <mujoco/mujoco.h>
|
||||||
|
|
||||||
|
|
||||||
// help
|
// help
|
||||||
static constexpr char helpstring[] =
|
static constexpr char kHelp[] =
|
||||||
"\n Usage: compile infile [outfile]\n"
|
"\n Usage: compile infile [outfile]\n"
|
||||||
" infile can be in mjcf, urdf, mjb format\n"
|
" infile can be in any format with a registered decoder (e.g. mjcf, "
|
||||||
" outfile can be in mjcf, mjb, txt format\n\n"
|
"urdf) or mjb\n"
|
||||||
" if infile is mjcf or urdf and outfile is omitted, a detailed\n"
|
" outfile can be in mjcf, mjb, txt format, or empty\n\n"
|
||||||
" timing breakdown is printed for two compilations (cold and warm cache)\n\n"
|
" if outfile is empty, compilation will be "
|
||||||
" Example: compile model.xml [model.mjb]\n";
|
"timed twice to measure the impact of caching\n\n"
|
||||||
|
" Example: compile model.xml [model.mjb]\n";
|
||||||
|
|
||||||
// timer (seconds)
|
|
||||||
double gettm(void) {
|
|
||||||
using Clock = std::chrono::steady_clock;
|
|
||||||
using Seconds = std::chrono::duration<double>;
|
|
||||||
static const Clock::time_point tm_start = Clock::now();
|
|
||||||
return Seconds(Clock::now() - tm_start).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// deallocate and print message
|
// deallocate and print message
|
||||||
int finish(const char* msg = 0, int exitcode = EXIT_SUCCESS, mjModel* m = 0) {
|
int finish(const char* msg = 0, int exitcode = EXIT_SUCCESS, mjModel* m = 0,
|
||||||
|
mjVFS* vfs = 0) {
|
||||||
// deallocated everything
|
// deallocated everything
|
||||||
if (m) {
|
if (m) {
|
||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
}
|
}
|
||||||
|
if (vfs) {
|
||||||
|
mj_deleteVFS(vfs);
|
||||||
|
}
|
||||||
|
|
||||||
// print message
|
// print message
|
||||||
if (msg) {
|
if (msg) {
|
||||||
@@ -57,52 +52,20 @@ int finish(const char* msg = 0, int exitcode = EXIT_SUCCESS, mjModel* m = 0) {
|
|||||||
return exitcode;
|
return exitcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if filename has extension (case-insensitive)
|
||||||
|
bool HasExtension(std::string_view filename, std::string_view ext) {
|
||||||
|
if (filename.length() < ext.length()) return false;
|
||||||
|
|
||||||
// possible file types
|
std::string_view file_ext = filename.substr(filename.length() - ext.length());
|
||||||
enum {
|
for (std::size_t i = 0; i < ext.length(); ++i) {
|
||||||
typeUNKNOWN = 0,
|
if (std::tolower(static_cast<unsigned char>(file_ext[i])) !=
|
||||||
typeXML,
|
std::tolower(static_cast<unsigned char>(ext[i]))) {
|
||||||
typeMJB,
|
return false;
|
||||||
typeTXT,
|
}
|
||||||
typeNONE
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// determine file type
|
|
||||||
int filetype(const char* filename) {
|
|
||||||
// convert to lower case for string comparison
|
|
||||||
char lower[1000];
|
|
||||||
std::size_t i=0;
|
|
||||||
while (i<std::strlen(filename) && i<999) {
|
|
||||||
lower[i] = (char)tolower(filename[i]);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
lower[i] = 0;
|
|
||||||
|
|
||||||
// find last dot
|
|
||||||
int dot = (int)std::strlen(lower);
|
|
||||||
while (dot>=0 && lower[dot]!='.') {
|
|
||||||
dot--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no dot found
|
|
||||||
if (dot<0) {
|
|
||||||
return typeUNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check extension
|
|
||||||
if (!std::strcmp(lower+dot, ".xml") || !std::strcmp(lower+dot, ".urdf")) {
|
|
||||||
return typeXML;
|
|
||||||
} else if (!std::strcmp(lower+dot, ".mjb")) {
|
|
||||||
return typeMJB;
|
|
||||||
} else if (!std::strcmp(lower+dot, ".txt")) {
|
|
||||||
return typeTXT;
|
|
||||||
} else {
|
|
||||||
return typeUNKNOWN;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// main function
|
// main function
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
|
|
||||||
@@ -111,34 +74,33 @@ int main(int argc, char** argv) {
|
|||||||
char error[1000];
|
char error[1000];
|
||||||
|
|
||||||
// print help if arguments are missing
|
// print help if arguments are missing
|
||||||
if (argc!=3 && argc!=2) {
|
if (argc != 3 && argc != 2) {
|
||||||
return finish(helpstring, EXIT_FAILURE);
|
return finish(kHelp, EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine file types
|
const bool is_mjb = HasExtension(argv[1], ".mjb");
|
||||||
int type1 = filetype(argv[1]);
|
|
||||||
int type2 = argc==2 ? typeNONE : filetype(argv[2]);
|
|
||||||
|
|
||||||
// check types
|
if (is_mjb && argc == 3 && HasExtension(argv[2], ".xml")) {
|
||||||
if (type1 == typeUNKNOWN || type1 == typeTXT ||
|
return finish("Illegal combination: cannot save binary model to XML",
|
||||||
type2 == typeUNKNOWN || (type1 == typeMJB && type2 == typeXML)) {
|
EXIT_FAILURE);
|
||||||
return finish("Illegal combination of file formats", EXIT_FAILURE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if output file exists
|
// check if output file exists
|
||||||
std::FILE* fp = std::fopen(argv[2], "r");
|
if (argc == 3) {
|
||||||
if (fp) {
|
std::FILE* fp = std::fopen(argv[2], "r");
|
||||||
std::cout << "Output file already exists, overwrite? (Y/n) ";
|
if (fp) {
|
||||||
char c;
|
|
||||||
std::cin >> c;
|
|
||||||
if (c!='y' && c!='Y') {
|
|
||||||
std::fclose(fp);
|
std::fclose(fp);
|
||||||
return finish();
|
std::cout << "Output file already exists, overwrite? (Y/n) ";
|
||||||
|
char c;
|
||||||
|
std::cin >> c;
|
||||||
|
if (c != 'y' && c != 'Y') {
|
||||||
|
return finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable compile timing diagnostics
|
// enable compile timing diagnostics
|
||||||
if (type2 == typeNONE) {
|
if (argc == 2) {
|
||||||
mjLogConfig config = mju_getLogConfig();
|
mjLogConfig config = mju_getLogConfig();
|
||||||
config.logfile[0] = '\0';
|
config.logfile[0] = '\0';
|
||||||
config.topics |= (1 << (mjTOPIC_TIME_CMP - 1));
|
config.topics |= (1 << (mjTOPIC_TIME_CMP - 1));
|
||||||
@@ -146,50 +108,58 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// load model
|
// load model
|
||||||
mjSpec* s = nullptr;
|
mjVFS vfs;
|
||||||
if (type1==typeXML) {
|
mj_defaultVFS(&vfs);
|
||||||
s = mj_parseXML(argv[1], 0, error, 1000);
|
mjSpec* spec = nullptr;
|
||||||
if (!s) {
|
|
||||||
return finish(error, EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type2 == typeNONE) {
|
if (is_mjb) {
|
||||||
std::cout << "Compile 1 (cold cache)\n";
|
m = mj_loadModel(argv[1], &vfs);
|
||||||
}
|
|
||||||
m = mj_compile(s, 0);
|
|
||||||
if (!m) {
|
if (!m) {
|
||||||
mj_deleteSpec(s);
|
return finish("Could not load binary model", EXIT_FAILURE, nullptr, &vfs);
|
||||||
return finish("Could not compile model", EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type2 == typeNONE) {
|
|
||||||
mj_deleteModel(m);
|
|
||||||
std::cout << "Compile 2 (warm cache)\n";
|
|
||||||
m = mj_compile(s, 0);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m = mj_loadModel(argv[1], 0);
|
spec = mj_parse(argv[1], nullptr, &vfs, error, 1000);
|
||||||
|
if (!spec) {
|
||||||
|
return finish(error, EXIT_FAILURE, nullptr, &vfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc == 2) {
|
||||||
|
std::cout << "Compile 1 (cold cache)\n";
|
||||||
|
}
|
||||||
|
m = mj_compile(spec, &vfs);
|
||||||
|
if (!m) {
|
||||||
|
auto err_msg = mjs_getError(spec);
|
||||||
|
mj_deleteSpec(spec);
|
||||||
|
return finish(err_msg, EXIT_FAILURE, nullptr, &vfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc == 2) {
|
||||||
|
mj_deleteModel(m);
|
||||||
|
std::cout << "Compile 2 (warm cache)\n";
|
||||||
|
m = mj_compile(spec, &vfs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check error
|
// check error
|
||||||
if (!m) {
|
if (!m) {
|
||||||
if (s) mj_deleteSpec(s);
|
if (spec) {
|
||||||
return finish("Could not load model", EXIT_FAILURE);
|
auto err_msg = mjs_getError(spec);
|
||||||
|
mj_deleteSpec(spec);
|
||||||
|
return finish(err_msg, EXIT_FAILURE, nullptr, &vfs);
|
||||||
|
} else {
|
||||||
|
return finish("Could not load model", EXIT_FAILURE, nullptr, &vfs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// save model
|
// encode output
|
||||||
if (type2 == typeXML) {
|
if (argc == 3) {
|
||||||
if (!mj_saveLastXML(argv[2], m, error, 1000)) {
|
if (mj_encode(spec, m, argv[2], nullptr, &vfs, error, 1000) < 0) {
|
||||||
if (s) mj_deleteSpec(s);
|
if (spec) mj_deleteSpec(spec);
|
||||||
return finish(error, EXIT_FAILURE, m);
|
return finish(error, EXIT_FAILURE, m, &vfs);
|
||||||
}
|
}
|
||||||
} else if (type2 == typeMJB) {
|
|
||||||
mj_saveModel(m, argv[2], 0, 0);
|
|
||||||
} else if (type2 == typeTXT) {
|
|
||||||
mj_printModel(m, argv[2]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// finalize
|
// finalize
|
||||||
if (s) mj_deleteSpec(s);
|
if (spec) mj_deleteSpec(spec);
|
||||||
return finish("Done.", EXIT_SUCCESS, m);
|
return finish("Done.", EXIT_SUCCESS, m, &vfs);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,4 +35,35 @@ if [ ! -s "$OUTPUT_FILE" ]; then
|
|||||||
die "Output file empty or missing (${OUTPUT_FILE})."
|
die "Output file empty or missing (${OUTPUT_FILE})."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Test invalid arguments (expect failure)
|
||||||
|
"$TARGET_BINARY" > /dev/null 2>&1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
die "Expected failure with no arguments"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test illegal combination (MJB -> XML, expect failure)
|
||||||
|
"$TARGET_BINARY" "$OUTPUT_FILE" "${TEST_TMPDIR}/failed.xml" > /dev/null 2>&1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
die "Expected failure for MJB -> XML"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test compile with 1 argument (expect success)
|
||||||
|
"$TARGET_BINARY" "$MODEL" > /dev/null || die "compile with 1 argument failed"
|
||||||
|
|
||||||
|
# Test XML -> MJZ (expect success)
|
||||||
|
OUTPUT_MJZ="${TEST_TMPDIR}/compiled.mjz"
|
||||||
|
"$TARGET_BINARY" "$MODEL" "$OUTPUT_MJZ" || die "compile to MJZ failed"
|
||||||
|
|
||||||
|
if [ ! -s "$OUTPUT_MJZ" ]; then
|
||||||
|
die "MJZ output file empty or missing (${OUTPUT_MJZ})."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test MJZ -> MJB (expect success)
|
||||||
|
OUTPUT_MJB_FROM_MJZ="${TEST_TMPDIR}/compiled_from_mjz.mjb"
|
||||||
|
"$TARGET_BINARY" "$OUTPUT_MJZ" "$OUTPUT_MJB_FROM_MJZ" || die "compile from MJZ to MJB failed"
|
||||||
|
|
||||||
|
if [ ! -s "$OUTPUT_MJB_FROM_MJZ" ]; then
|
||||||
|
die "MJB output from MJZ empty or missing (${OUTPUT_MJB_FROM_MJZ})."
|
||||||
|
fi
|
||||||
|
|
||||||
echo "PASS"
|
echo "PASS"
|
||||||
|
|||||||
Reference in New Issue
Block a user