-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fix inconsistent asset paths in mj_saveLastXML: canonicalize includes… #2954
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,23 +49,48 @@ class GlobalModel { | |
| void Set(mjSpec* spec = nullptr); | ||
|
|
||
| // writes XML to string | ||
| std::optional<std::string> ToXML(const mjModel* m, char* error, | ||
| int error_sz); | ||
| // out_dir: optional. If non-null, temporarily set spec->modelfiledir to out_dir | ||
| // so the writer can emit asset paths relative to the output directory. | ||
| std::optional<std::string> ToXML(const mjModel* m, const char* out_dir, | ||
| char* error, int error_sz); | ||
|
|
||
| private: | ||
| // using raw pointers as GlobalModel needs to be trivially destructible | ||
| std::mutex* mutex_ = new std::mutex(); | ||
| mjSpec* spec_ = nullptr; | ||
| }; | ||
|
|
||
| std::optional<std::string> GlobalModel::ToXML(const mjModel* m, char* error, | ||
| int error_sz) { | ||
| std::optional<std::string> GlobalModel::ToXML(const mjModel* m, const char* out_dir, | ||
| char* error, int error_sz) { | ||
| std::lock_guard<std::mutex> lock(*mutex_); | ||
| if (!spec_) { | ||
| mjCopyError(error, "No XML model loaded", error_sz); | ||
| return std::nullopt; | ||
| } | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'd rather just pass the output directory to WriteXML and let it sort out relative pathing. I'm also not sure that this will do what is claimed, WriteXML as far as I can tell seems to grab file paths from the spec. These will have been resolved at parse/compile time, so changing the modelfiledir here feels like it won't have the desired effect. Could you write some tests to ensure correctness? |
||
| // If out_dir is provided, temporarily override spec->modelfiledir so | ||
| // the writer can compute relative paths against the output file location. | ||
| std::string old_modelfiledir; | ||
| bool changed_modelfiledir = false; | ||
| if (out_dir && out_dir[0] != '\0') { | ||
| // Save old value | ||
| const char* old = mjs_getString(spec_->modelfiledir); | ||
| if (old) old_modelfiledir = old; | ||
| else old_modelfiledir.clear(); | ||
|
|
||
| // Set to requested out_dir | ||
| mjs_setString(spec_->modelfiledir, out_dir); | ||
| changed_modelfiledir = true; | ||
| } | ||
|
|
||
| // Call the writer (WriteXML relies on spec_->modelfiledir when emitting paths). | ||
| std::string result = WriteXML(m, spec_, error, error_sz); | ||
|
|
||
| // Restore old modelfiledir if we changed it | ||
| if (changed_modelfiledir) { | ||
| mjs_setString(spec_->modelfiledir, old_modelfiledir.empty() ? "" : old_modelfiledir.c_str()); | ||
| } | ||
|
|
||
| if (result.empty()) { | ||
| return std::nullopt; | ||
| } | ||
|
|
@@ -160,6 +185,16 @@ mjModel* mj_loadUSD(const char* filename, const mjVFS* vfs, char* error, int err | |
| } | ||
| #endif | ||
|
|
||
| // helper: compute dirname of path (returns empty string if no dir component) | ||
| static std::string DirnameOfPath(const char* path) { | ||
| if (!path) return std::string(); | ||
| std::string s(path); | ||
| // find last slash or backslash | ||
| size_t pos = s.find_last_of("/\\"); | ||
| if (pos == std::string::npos) return std::string(); | ||
| return s.substr(0, pos); | ||
| } | ||
|
|
||
| // update XML data structures with info from low-level model, save as MJCF | ||
| // returns 1 if successful, 0 otherwise | ||
| // error can be NULL; otherwise assumed to have size error_sz | ||
|
|
@@ -173,7 +208,12 @@ int mj_saveLastXML(const char* filename, const mjModel* m, char* error, int erro | |
| } | ||
| } | ||
|
|
||
| auto result = GetGlobalModel().ToXML(m, error, error_sz); | ||
| // compute output directory (if any) so writer can emit relative paths w.r.t. it | ||
| std::string out_dir = DirnameOfPath(filename ? filename : ""); | ||
| const char* out_dir_ptr = nullptr; | ||
| if (!out_dir.empty()) out_dir_ptr = out_dir.c_str(); | ||
|
|
||
| auto result = GetGlobalModel().ToXML(m, out_dir_ptr, error, error_sz); | ||
| if (result.has_value()) { | ||
| fprintf(fp, "%s", result->c_str()); | ||
| } | ||
|
|
@@ -195,6 +235,7 @@ void mj_freeLastXML(void) { | |
|
|
||
|
|
||
|
|
||
|
|
||
| // print internal XML schema as plain text or HTML, with style-padding or | ||
| int mj_printSchema(const char* filename, char* buffer, int buffer_sz, int flg_html, int flg_pad) { | ||
| // print to stringstream | ||
|
|
@@ -253,7 +294,7 @@ mjSpec* mj_parseXML(const char* filename, const mjVFS* vfs, char* error, int err | |
|
|
||
|
|
||
|
|
||
| // parse spec from string | ||
| // parse spec from string | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: errant space here? |
||
| mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error, int error_sz) { | ||
| return ParseSpecFromString(xml, vfs, error, error_sz); | ||
| } | ||
|
|
@@ -294,4 +335,3 @@ int mj_saveXMLString(const mjSpec* s, char* xml, int xml_sz, char* error, int er | |
| xml[result.size()] = 0; | ||
| return 0; | ||
| } | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Please keep ending new line. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably just replace filename usages below with fullname and make it clear.