Add content_type attribute that takes a MIME type to specify the file contents for meshes, hfields, and textures.

PiperOrigin-RevId: 549302344
Change-Id: I30a8adfef5b5d87bb20479329f3396892f5137ff
This commit is contained in:
Kyle Bayes
2023-07-19 06:50:35 -07:00
committed by Copybara-Service
parent 5f327b0c31
commit 5fcdae771b
12 changed files with 556 additions and 44 deletions
+29 -5
View File
@@ -2081,7 +2081,10 @@ This adds an element to the tuple.
This is a grouping element for defining assets. It does not have attributes. Assets are created in the model so that
they can be referenced from other model elements; recall the discussion of :ref:`Assets <Assets>` in the Overview
chapter.
chapter. Assets opened from a file can be identified in two different ways: filename extensions or the ``content_type``
attribute. MuJoCo will attempt to open a file specified by the content type provided, and only defaults to the filename
extension if no ``content_type`` attribute is specified. The content type is ignored if the asset isn't loaded from a
file.
.. _asset-texture:
@@ -2097,8 +2100,8 @@ chapter.
textures. Alternatively the data can be generated by the compiler as a procedural texture. Because different texture
types require different parameters, only a subset of the attributes below are used for any given texture.
| MuJoCo 2.0 introduced a second file format for loading textures, in addition to PNG. If the file name extension is
different from .png or .PNG, MuJoCo assumes that the texture is in the new format. This is a custom binary file
format, containing the following data:
different from .png or .PNG, or if the ``content_type`` attribute is set to ``image/vnd.mujoco.texture``, then MuJoCo
assumes that the texture is in the new format. This is a custom binary file format, containing the following data:
.. code:: Text
@@ -2153,6 +2156,13 @@ chapter.
texrepeat attribute of :ref:`material <asset-material>`. The data can be loaded from a singlefile or created
procedurally.
.. _asset-texture-content_type:
:at:`content_type`: :at-val:`string, optional`
If the file attribute is specified, then this sets the `MIME <https://en.wikipedia.org/wiki/MIME>`_ type of the file
to be loaded. Any filename extensions will be ignored. Currently ``image/png`` and ``image/vnd.mujoco.texture`` are
supported.
.. _asset-texture-file:
:at:`file`: :at-val:`string, optional`
@@ -2335,6 +2345,13 @@ also known as terrain map, is a 2D matrix of elevation data. The data can be spe
Name of the height field, used for referencing. If the name is omitted and a file name is specified, the height field
name equals the file name without the path and extension.
.. _asset-hfield-content_type:
:at:`content_type`: :at-val: `string, optional`
If the file attribute is specified, then this sets the `MIME <https://en.wikipedia.org/wiki/MIME>`_ type of the file
to be loaded. Any filename extensions will be overloaded. Currently ``image/png`` and ``image/vnd.mujoco.hfield``
are supported.
.. _asset-hfield-file:
:at:`file`: :at-val:`string, optional`
@@ -2500,12 +2517,19 @@ The full list of processing steps applied by the compiler to each mesh is as fol
:at:`class`: :at-val:`string, optional`
Defaults class for setting unspecified attributes (only scale in this case).
.. _asset-mesh-content_type:
:at:`content_type`: :at-val:`string, optional`
If the file attribute is specified, then this sets the `MIME <https://en.wikipedia.org/wiki/MIME>`_ type of the file
to be loaded. Any filename extensions will be overloaded. Currently ``model/vnd.mujoco.msh``, ``model/obj``, and
``model/stl`` are supported.
.. _asset-mesh-file:
:at:`file`: :at-val:`string, optional`
The file from which the mesh will be loaded. The path is determined as described in the meshdir attribute of
:ref:`compiler <compiler>`. The file extension must be "stl" or "msh" (not case sensitive) specifying the file type.
If the file name is omitted, the vertex attribute becomes required.
:ref:`compiler <compiler>`. The file extension must be "stl", "msh", or "obj" (not case sensitive) specifying the
file type. If the file name is omitted, the vertex attribute becomes required.
.. _asset-mesh-scale:
+15 -13
View File
@@ -509,41 +509,43 @@
| :ref:`texture | \* | :class: mjcf-attributes |
| <asset-texture>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`name<asset-texture-name>` | :ref:`type<asset-texture-type>` | :ref:`file<asset-texture-file>` | |
| | | | :ref:`name<asset-texture-name>` | :ref:`type<asset-texture-type>` | :ref:`content_type<asset-texture-content_type>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`gridsize<asset-texture-gridsize>` | :ref:`gridlayout<asset-texture-gridlayout>` | :ref:`fileright<asset-texture-fileright>` | |
| | | | :ref:`file<asset-texture-file>` | :ref:`gridsize<asset-texture-gridsize>` | :ref:`gridlayout<asset-texture-gridlayout>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`fileleft<asset-texture-fileleft>` | :ref:`fileup<asset-texture-fileup>` | :ref:`filedown<asset-texture-filedown>` | |
| | | | :ref:`fileright<asset-texture-fileright>` | :ref:`fileleft<asset-texture-fileleft>` | :ref:`fileup<asset-texture-fileup>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`filefront<asset-texture-filefront>` | :ref:`fileback<asset-texture-fileback>` | :ref:`builtin<asset-texture-builtin>` | |
| | | | :ref:`filedown<asset-texture-filedown>` | :ref:`filefront<asset-texture-filefront>` | :ref:`fileback<asset-texture-fileback>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`rgb1<asset-texture-rgb1>` | :ref:`rgb2<asset-texture-rgb2>` | :ref:`mark<asset-texture-mark>` | |
| | | | :ref:`builtin<asset-texture-builtin>` | :ref:`rgb1<asset-texture-rgb1>` | :ref:`rgb2<asset-texture-rgb2>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`markrgb<asset-texture-markrgb>` | :ref:`random<asset-texture-random>` | :ref:`width<asset-texture-width>` | |
| | | | :ref:`mark<asset-texture-mark>` | :ref:`markrgb<asset-texture-markrgb>` | :ref:`random<asset-texture-random>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`height<asset-texture-height>` | :ref:`hflip<asset-texture-hflip>` | :ref:`vflip<asset-texture-vflip>` | |
| | | | :ref:`width<asset-texture-width>` | :ref:`height<asset-texture-height>` | :ref:`hflip<asset-texture-hflip>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`vflip<asset-texture-vflip>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| asset |br| |_| |L| | | .. table:: |
| :ref:`hfield | \* | :class: mjcf-attributes |
| <asset-hfield>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`name<asset-hfield-name>` | :ref:`file<asset-hfield-file>` | :ref:`nrow<asset-hfield-nrow>` | |
| | | | :ref:`name<asset-hfield-name>` | :ref:`content_type<asset-hfield-content_type>` | :ref:`file<asset-hfield-file>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`ncol<asset-hfield-ncol>` | :ref:`size<asset-hfield-size>` | | |
| | | | :ref:`nrow<asset-hfield-nrow>` | :ref:`ncol<asset-hfield-ncol>` | :ref:`size<asset-hfield-size>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| asset |br| |_| |L| | | .. table:: |
| :ref:`mesh | \* | :class: mjcf-attributes |
| <asset-mesh>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`name<asset-mesh-name>` | :ref:`class<asset-mesh-class>` | :ref:`file<asset-mesh-file>` | |
| | | | :ref:`name<asset-mesh-name>` | :ref:`class<asset-mesh-class>` | :ref:`content_type<asset-mesh-content_type>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`vertex<asset-mesh-vertex>` | :ref:`normal<asset-mesh-normal>` | :ref:`texcoord<asset-mesh-texcoord>` | |
| | | | :ref:`file<asset-mesh-file>` | :ref:`vertex<asset-mesh-vertex>` | :ref:`normal<asset-mesh-normal>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`face<asset-mesh-face>` | :ref:`refpos<asset-mesh-refpos>` | :ref:`refquat<asset-mesh-refquat>` | |
| | | | :ref:`texcoord<asset-mesh-texcoord>` | :ref:`face<asset-mesh-face>` | :ref:`refpos<asset-mesh-refpos>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`scale<asset-mesh-scale>` | :ref:`smoothnormal<asset-mesh-smoothnormal>` | | |
| | | | :ref:`refquat<asset-mesh-refquat>` | :ref:`scale<asset-mesh-scale>` | :ref:`smoothnormal<asset-mesh-smoothnormal>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| asset |br| |_| |L| | | .. table:: |
+4 -1
View File
@@ -9,11 +9,14 @@ General
^^^^^^^
- Added primitive collider for sphere-cylinder contacts, previously this pair used the generic convex-convex collider.
- Added an optional ``content_type`` attribute to hfield, texture, and mesh assets. This attribute supports a formatted
`MIME <https://en.wikipedia.org/wiki/MIME>`_ string used to determine the type of the asset file without resorting to
pulling the type from the file extension.
- Added analytic derivatives for quaternion :ref:`subtraction<mjd_subQuat>` and :ref:`integration<mjd_quatIntegrate>`
(rotation with an angular velocity). Derivatives are in the 3D tangent space.
- Added :ref:`mjv_connector` which has identical functionality to :ref:`mjv_makeConnector`, but with more convenient
"from-to" argument parametrization. :ref:`mjv_makeConnector` is now deprecated.
- Bump oldest supported MacOS from version 10.12 to 11. MacOS 11 is the oldest version still maintained by Apple.
- Bumped oldest supported MacOS from version 10.12 to 11. MacOS 11 is the oldest version still maintained by Apple.
Python bindings
^^^^^^^^^^^^^^^
+10 -8
View File
@@ -188,21 +188,23 @@ void mjCMesh::Compile(int vfs_provider) {
file = mjuu_strippath(file);
}
// load STL, OBJ or MSH
string ext = mjuu_getext(file);
if (strcasecmp(ext.c_str(), ".stl") &&
strcasecmp(ext.c_str(), ".obj") &&
strcasecmp(ext.c_str(), ".msh")) {
throw mjCError(this, "Unknown mesh file type: %s", file.c_str());
std::string asset_type = GetAssetContentType(file, content_type);
if (asset_type.empty()) {
throw mjCError(this, "unknown mesh content type for file: '%s'", file.c_str());
}
if (asset_type != "model/stl" && asset_type != "model/obj"
&& asset_type != "model/vnd.mujoco.msh") {
throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str());
}
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
mjResource* resource = LoadResource(filename, vfs_provider);
try {
if (!strcasecmp(ext.c_str(), ".stl")) {
if (asset_type == "model/stl") {
LoadSTL(resource);
} else if (!strcasecmp(ext.c_str(), ".obj")) {
} else if (asset_type == "model/obj") {
LoadOBJ(resource);
} else {
LoadMSH(resource);
+43 -10
View File
@@ -507,6 +507,23 @@ mjResource* mjCBase::LoadResource(string filename, int provider) {
}
// Get and sanitize content type from raw_text if not empty, otherwise parse
// content type from resource_name; throw error on failure
std::string mjCBase::GetAssetContentType(std::string_view resource_name,
std::string_view raw_text) {
if (!raw_text.empty()) {
auto type = mjuu_parseContentTypeAttrType(raw_text);
auto subtype = mjuu_parseContentTypeAttrSubtype(raw_text);
if (!type.has_value() || !subtype.has_value()) {
throw mjCError(this, "invalid format for content_type");
}
return std::string(*type) + "/" + std::string(*subtype);
} else {
return mjuu_extToContentType(resource_name);
}
}
//------------------ class mjCBody implementation --------------------------------------------------
// constructor
@@ -2149,15 +2166,22 @@ void mjCHField::Compile(int vfs_provider) {
"hfield '%s' (id = %d) specified from file and manually", name.c_str(), id);
}
// make filename
std::string asset_type = GetAssetContentType(file, content_type);
// fallback to custom
if (asset_type.empty()) {
asset_type = "image/vnd.mujoco.hfield";
}
if (asset_type != "image/png" && asset_type != "image/vnd.mujoco.hfield") {
throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str());
}
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
mjResource* resource = LoadResource(filename, vfs_provider);
// load depending on format
string ext = mjuu_getext(filename);
try {
if (!strcasecmp(ext.c_str(), ".png")) {
if (asset_type == "image/png") {
LoadPNG(resource);
} else {
LoadCustom(resource);
@@ -2548,15 +2572,24 @@ void mjCTexture::LoadCustom(mjResource* resource,
void mjCTexture::LoadFlip(string filename, int vfs_provider,
std::vector<unsigned char>& image,
unsigned int& w, unsigned int& h) {
// dispatch to PNG or Custom loaded
string ext = mjuu_getext(filename);
std::string asset_type = GetAssetContentType(filename, content_type);
// fallback to custom
if (asset_type.empty()) {
asset_type = "image/vnd.mujoco.texture";
}
if (asset_type != "image/png" && asset_type != "image/vnd.mujoco.texture") {
throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str());
}
mjResource* resource = LoadResource(filename, vfs_provider);
try {
if (!strcasecmp(ext.c_str(), ".png")) {
LoadPNG(resource, image, w, h);
if (asset_type == "image/png") {
LoadPNG(resource, image, w, h);
} else {
LoadCustom(resource, image, w, h);
LoadCustom(resource, image, w, h);
}
mju_closeResource(resource);
} catch(mjCError err) {
+7
View File
@@ -173,6 +173,10 @@ class mjCBase {
// load resource if found (fallback to OS filesystem)
mjResource* LoadResource(std::string filename, int provider);
// Get and sanitize content type from raw_text if not empty, otherwise parse
// content type from resource_name; throw on failure
std::string GetAssetContentType(std::string_view resource_name, std::string_view raw_text);
std::string name; // object name
std::string classname; // defaults class name
int id; // object id
@@ -529,6 +533,7 @@ class mjCMesh: public mjCBase {
// returns a bounding volume given a face
mjCBoundingVolume GetBoundingVolume(int faceid);
std::string content_type; // content type of file
std::string file; // mesh file
double refpos[3]; // reference position (translate)
double refquat[4]; // reference orientation (rotate)
@@ -652,6 +657,7 @@ class mjCHField : public mjCBase {
friend class mjXWriter;
public:
std::string content_type; // content type of file
std::string file; // file: (nrow, ncol, [elevation data])
double size[4]; // hfield size (ignore referencing geom size)
int nrow; // number of rows
@@ -691,6 +697,7 @@ class mjCTexture : public mjCBase {
int width; // width in pixels
// method 2: single file
std::string content_type; // content type of file
std::string file; // png file to load; use for all sides of cube
int gridsize[2]; // size of grid for composite file; (1,1)-repeat
char gridlayout[13]; // row-major: L,R,F,B,U,D for faces; . for unused
+81
View File
@@ -18,12 +18,14 @@
#include <cstddef>
#include <cstdio>
#include <limits>
#include <optional>
#include <string>
#include <string_view>
#include <mujoco/mjmacro.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mujoco.h>
#include "engine/engine_crossplatform.h"
#include "engine/engine_util_spatial.h"
using std::isnan;
@@ -596,3 +598,82 @@ string mjuu_makefullname(string filedir, string meshdir, string filename) {
// default
return filedir + meshdir + filename;
}
// return true if the text is in a valid content type format:
// {type}/{subtype}[;{parameter}={value}]
static bool mjuu_isValidContentType(std::string_view text) {
// find a forward slash that's not the last character
size_t n = text.find('/');
if (n == std::string::npos || n == text.size() - 1) {
return false;
}
size_t m = text.find(';');
if (m == std::string::npos) {
return true;
}
if (m + 1 <= n) {
return false;
}
// just check if there's an equal sign; this isn't robust enough for general
// validation, but works for our scope, hence this is a private helper
// function
size_t s = text.find('=');
if (s == std::string::npos || s + 1 <= m) {
return false;
}
return true;
}
// return type from content_type format {type}/{subtype}[;{parameter}={value}]
// return empty string on invalid format
std::optional<std::string_view> mjuu_parseContentTypeAttrType(std::string_view text) {
if (!mjuu_isValidContentType(text)) {
return std::nullopt;
}
return { text.substr(0, text.find('/')) };
}
// return subtype from content_type format {type}/{subtype}[;{parameter}={value}]
// return empty string on invalid format
std::optional<std::string_view> mjuu_parseContentTypeAttrSubtype(std::string_view text) {
if (!mjuu_isValidContentType(text)) {
return std::nullopt;
}
size_t n = text.find('/');
size_t m = text.find(';', n + 1);
if (m == std::string::npos) {
return { text.substr(n+1) };
}
return { text.substr(n + 1, m - n - 1) };
}
// convert filename extension to content type; return empty string if not found
std::string mjuu_extToContentType(std::string_view filename) {
std::string ext = mjuu_getext(filename);
if (!strcasecmp(ext.c_str(), ".stl")) {
return "model/stl";
} else if (!strcasecmp(ext.c_str(), ".obj")) {
return "model/obj";
} else if (!strcasecmp(ext.c_str(), ".msh")) {
return "model/vnd.mujoco.msh";
} else if (!strcasecmp(ext.c_str(), ".png")) {
return "image/png";
} else {
return "";
}
}
+10 -1
View File
@@ -15,6 +15,7 @@
#ifndef MUJOCO_SRC_USER_USER_UTIL_H_
#define MUJOCO_SRC_USER_USER_UTIL_H_
#include <optional>
#include <string>
#include <string_view>
@@ -23,7 +24,6 @@ extern const double mjNAN; // used to mark undefined fields
const double mjEPS = 1E-14; // minimum value in various calculations
const double mjMINMASS = 1E-6; // minimum mass allowed
// check if numeric variable is defined: !_isnan(num)
bool mjuu_defined(const double num);
@@ -152,4 +152,13 @@ bool mjuu_isabspath(std::string path);
// assemble full filename
std::string mjuu_makefullname(std::string filedir, std::string meshdir, std::string filename);
// return type from content_type format {type}/{subtype}[;{parameter}={value}]
std::optional<std::string_view> mjuu_parseContentTypeAttrType(std::string_view text);
// return subtype from content_type format {type}/{subtype}[;{parameter}={value}]
std::optional<std::string_view> mjuu_parseContentTypeAttrSubtype(std::string_view text);
// convert filename extension to content type; return empty string if not found
std::string mjuu_extToContentType(std::string_view filename);
#endif // MUJOCO_SRC_USER_USER_UTIL_H_
+6 -3
View File
@@ -212,12 +212,12 @@ static const char* MJCF[nMJCF][mjXATTRNUM] = {
{"asset", "*", "0"},
{"<"},
{"texture", "*", "21", "name", "type", "file", "gridsize", "gridlayout",
{"texture", "*", "22", "name", "type", "content_type", "file", "gridsize", "gridlayout",
"fileright", "fileleft", "fileup", "filedown", "filefront", "fileback",
"builtin", "rgb1", "rgb2", "mark", "markrgb", "random", "width", "height",
"hflip", "vflip"},
{"hfield", "*", "5", "name", "file", "nrow", "ncol", "size"},
{"mesh", "*", "11", "name", "class", "file", "vertex", "normal",
{"hfield", "*", "6", "name", "content_type", "file", "nrow", "ncol", "size"},
{"mesh", "*", "12", "name", "class", "content_type", "file", "vertex", "normal",
"texcoord", "face", "refpos", "refquat", "scale", "smoothnormal"},
{"skin", "*", "9", "name", "file", "material", "rgba", "inflate",
"vertex", "texcoord", "face", "group"},
@@ -1186,6 +1186,7 @@ void mjXReader::OneMesh(XMLElement* elem, mjCMesh* pmesh) {
// read attributes
ReadAttrTxt(elem, "name", pmesh->name);
ReadAttrTxt(elem, "class", pmesh->classname);
ReadAttrTxt(elem, "content_type", pmesh->content_type);
ReadAttrTxt(elem, "file", pmesh->file);
ReadAttr(elem, "refpos", 3, pmesh->refpos, text);
ReadAttr(elem, "refquat", 4, pmesh->refquat, text);
@@ -2511,6 +2512,7 @@ void mjXReader::Asset(XMLElement* section) {
ptex->type = (mjtTexture)n;
}
ReadAttrTxt(elem, "name", ptex->name);
ReadAttrTxt(elem, "content_type", ptex->content_type);
ReadAttrTxt(elem, "file", ptex->file);
ReadAttrInt(elem, "width", &ptex->width);
ReadAttrInt(elem, "height", &ptex->height);
@@ -2583,6 +2585,7 @@ void mjXReader::Asset(XMLElement* section) {
// read attributes
ReadAttrTxt(elem, "name", phf->name);
ReadAttrTxt(elem, "content_type", phf->content_type);
ReadAttrTxt(elem, "file", phf->file);
ReadAttrInt(elem, "nrow", &phf->nrow);
ReadAttrInt(elem, "ncol", &phf->ncol);
+5 -2
View File
@@ -90,6 +90,7 @@ void mjXWriter::OneMesh(XMLElement* elem, mjCMesh* pmesh, mjCDef* def) {
if (!writingdefaults) {
WriteAttrTxt(elem, "name", pmesh->name);
WriteAttrTxt(elem, "class", pmesh->classname);
WriteAttrTxt(elem, "content_type", pmesh->content_type);
WriteAttrTxt(elem, "file", pmesh->file);
// write vertex data
@@ -1255,9 +1256,10 @@ void mjXWriter::Asset(XMLElement* root) {
WriteAttrInt(elem, "height", ptex->height);
}
// write texures loaded from files
// write textures loaded from files
else {
// write singe file
// write single file
WriteAttrTxt(elem, "content_type", ptex->content_type);
WriteAttrTxt(elem, "file", ptex->file);
// write separate files
@@ -1317,6 +1319,7 @@ void mjXWriter::Asset(XMLElement* root) {
WriteAttrTxt(elem, "name", phf->name);
WriteAttr(elem, "size", 4, phf->size);
if (!phf->file.empty()) {
WriteAttrTxt(elem, "content_type", phf->content_type);
WriteAttrTxt(elem, "file", phf->file);
} else {
WriteAttrInt(elem, "nrow", phf->nrow);
+128 -1
View File
@@ -88,7 +88,7 @@ TEST_F(MjCMeshTest, UnknownMeshFormat) {
LoadModelFromString(xml.c_str(), error.data(), error.size());
ASSERT_THAT(model, testing::IsNull())
<< "Should fail to load a mesh named: " << name;
EXPECT_THAT(error.data(), HasSubstr("Unknown mesh file type"));
EXPECT_THAT(error.data(), HasSubstr("unknown mesh content type for file"));
EXPECT_THAT(error.data(), HasSubstr(name));
}
}
@@ -170,6 +170,133 @@ TEST_F(MjCMeshTest, LoadSTLWithVFS) {
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
}
// ------------- test content_type attributes ----------------------------------
TEST_F(MjCMeshTest, LoadMSHWithContentType) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<mesh name="mesh1" content_type="model/vnd.mujoco.msh" file="some_file"/>
</asset>
<worldbody>
<geom type="mesh" mesh="mesh1"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
// should try opening the file (not found obviously)
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
}
TEST_F(MjCMeshTest, LoadOBJWithContentType) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<mesh name="mesh1" content_type="model/obj" file="some_file"/>
</asset>
<worldbody>
<geom type="mesh" mesh="mesh1"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
// should try opening the file (not found obviously)
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
}
TEST_F(MjCMeshTest, LoadSTLWithContentType) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<mesh name="mesh1" content_type="model/stl" file="some_file"/>
</asset>
<worldbody>
<geom type="mesh" mesh="mesh1"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
// should try opening the file (not found obviously)
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
}
TEST_F(MjCMeshTest, LoadMSHWithContentTypeError) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<mesh name="mesh1" content_type="model/unknown" file="some_file"/>
</asset>
<worldbody>
<geom type="mesh" mesh="mesh1"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
// should error with unknown file type
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("unsupported content type: 'model/unknown'"));
}
TEST_F(MjCMeshTest, LoadMSHWithContentTypeParam) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<mesh name="mesh1" content_type="model/vnd.mujoco.msh;parameter=value" file="some_file"/>
</asset>
<worldbody>
<geom type="mesh" mesh="mesh1"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
// should try opening the file (not found obviously)
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
}
// ------------- test vertex de-duplication (STL) ------------------------------
TEST_F(MjCMeshTest, DeDuplicateSTLVertices) {
+218
View File
@@ -150,6 +150,224 @@ TEST_F(VfsTest, TextureCustomWithVFS) {
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
}
// ------------------------ test content_type attribute ------------------------
using ContentTypeTest = MujocoTest;
TEST_F(ContentTypeTest, HFieldPngWithContentType) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<hfield name="hfield" content_type="image/png"
file="some_file.png" size="0.5 0.5 1 0.1"/>
</asset>
<worldbody>
<geom type="hfield" hfield="hfield" pos="-.4 .6 .05"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
// should try loading the file
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
}
TEST_F(ContentTypeTest, HFieldCustomWithContentType) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<hfield name="hfield" content_type="image/vnd.mujoco.hfield"
file="some_file" size="0.5 0.5 1 0.1"/>
</asset>
<worldbody>
<geom type="hfield" hfield="hfield" pos="-.4 .6 .05"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
// should try loading the file
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
}
TEST_F(ContentTypeTest, HFieldWithContentTypeError) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<hfield name="hfield" content_type="image/jpeg"
file="some_file" size="0.5 0.5 1 0.1"/>
</asset>
<worldbody>
<geom type="hfield" hfield="hfield" pos="-.4 .6 .05"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
// should try loading the file
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("unsupported content type: 'image/jpeg'"));
}
TEST_F(ContentTypeTest, TexturePngWithContentType) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<texture name="texture" content_type="image/png" file="some_file" type="2d"/>
<material name="material" texture="texture"/>
</asset>
<worldbody>
<geom type="plane" material="material" size="4 4 4"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
// should try loading the file
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
}
TEST_F(ContentTypeTest, TextureCustomWithContentType) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<texture name="texture" content_type="image/vnd.mujoco.texture"
file="some_file" type="2d"/>
<material name="material" texture="texture"/>
</asset>
<worldbody>
<geom type="plane" material="material" size="4 4 4"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
// should try loading the file
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
}
TEST_F(ContentTypeTest, TextureWithContentTypeError) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<texture name="texture" content_type="image/jpeg"
file="some_file" type="2d"/>
<material name="material" texture="texture"/>
</asset>
<worldbody>
<geom type="plane" material="material" size="4 4 4"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
// should try loading the file
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("unsupported content type: 'image/jpeg'"));
}
TEST_F(ContentTypeTest, TextureLoadPng) {
static constexpr char filename[] = "tiny";
static constexpr char xml[] = R"(
<mujoco>
<asset>
<texture content_type="image/png" file="tiny" type="2d"/>
<material name="material" texture="tiny"/>
</asset>
<worldbody>
<geom type="plane" material="material" size="4 4 4"/>
</worldbody>
</mujoco>
)";
// credit: https://www.mjt.me.uk/posts/smallest-png/
static constexpr unsigned char tiny[] =
{ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00,
0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x03, 0x00,
0x00, 0x00, 0x66, 0xBC, 0x3A, 0x25, 0x00, 0x00, 0x00,
0x03, 0x50, 0x4C, 0x54, 0x45, 0xB5, 0xD0, 0xD0, 0x63,
0x04, 0x16, 0xEA, 0x00, 0x00, 0x00, 0x1F, 0x49, 0x44,
0x41, 0x54, 0x68, 0x81, 0xED, 0xC1, 0x01, 0x0D, 0x00,
0x00, 0x00, 0xC2, 0xA0, 0xF7, 0x4F, 0x6D, 0x0E, 0x37,
0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xBE, 0x0D, 0x21, 0x00, 0x00, 0x01, 0x9A, 0x60, 0xE1,
0xD5, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44,
0xAE, 0x42, 0x60, 0x82 };
size_t tiny_sz = sizeof(tiny);
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
mj_makeEmptyFileVFS(vfs.get(), filename, 105);
int i = mj_findFileVFS(vfs.get(), filename);
memcpy(vfs->filedata[i], tiny, tiny_sz);
// loading the file should be successful
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, NotNull());
mj_deleteModel(model);
mj_deleteFileVFS(vfs.get(), filename);
}
// ------------------------ test keyframes -------------------------------------
using KeyframeTest = MujocoTest;