Create mjpDecoder, mj_parse, and mju_decodeResource

- plugin system similar to mjpResourceProvider, but instead of loading a resource it converts an existing mjResource into an mjSpec.
  - The returned spec is then composed into the referencing spec.
  - This enables different file types to generate arbitrary specs, and allows us to separate format parsing from compilation code.

Follow up CLs will move some of the logic in src/engine for PNG, USD, KTX, OBJ loading into decoders.

The mj_parse function MjSpec from a given file, it's a more generic version of mj_parseXML.

In it's implementation, mj_parse as opposed to mj_parseXML will look for any registered decoder and
not assume we are striclty dealing with MJCF.

PiperOrigin-RevId: 826149497
Change-Id: I0ece26904280cb94bd5ded6dd5a565c539d60254
This commit is contained in:
Sam Haves
2025-10-30 12:53:40 -07:00
committed by Copybara-Service
parent f2badc05ac
commit 57f7145806
16 changed files with 2063 additions and 1555 deletions
@@ -39,6 +39,8 @@ ClangJsonNode = Mapping[str, Any]
_ANONYMOUS_KEY_PATTERN = re.compile(r'\d+:\d+(?=\))')
_EXCLUDED = (
'mjpDecoder',
'mjpDecoder_',
'mjpPlugin',
'mjpPlugin_',
'mjpResourceProvider',
+99 -99
View File
@@ -553,6 +553,105 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjNTIMER', 15),
]),
)),
('mjtGeomInertia',
EnumDecl(
name='mjtGeomInertia',
declname='enum mjtGeomInertia_',
values=dict([
('mjINERTIA_VOLUME', 0),
('mjINERTIA_SHELL', 1),
]),
)),
('mjtMeshInertia',
EnumDecl(
name='mjtMeshInertia',
declname='enum mjtMeshInertia_',
values=dict([
('mjMESH_INERTIA_CONVEX', 0),
('mjMESH_INERTIA_EXACT', 1),
('mjMESH_INERTIA_LEGACY', 2),
('mjMESH_INERTIA_SHELL', 3),
]),
)),
('mjtMeshBuiltin',
EnumDecl(
name='mjtMeshBuiltin',
declname='enum mjtMeshBuiltin_',
values=dict([
('mjMESH_BUILTIN_NONE', 0),
('mjMESH_BUILTIN_SPHERE', 1),
('mjMESH_BUILTIN_HEMISPHERE', 2),
('mjMESH_BUILTIN_CONE', 3),
('mjMESH_BUILTIN_SUPERSPHERE', 4),
('mjMESH_BUILTIN_SUPERTORUS', 5),
('mjMESH_BUILTIN_WEDGE', 6),
('mjMESH_BUILTIN_PLATE', 7),
]),
)),
('mjtBuiltin',
EnumDecl(
name='mjtBuiltin',
declname='enum mjtBuiltin_',
values=dict([
('mjBUILTIN_NONE', 0),
('mjBUILTIN_GRADIENT', 1),
('mjBUILTIN_CHECKER', 2),
('mjBUILTIN_FLAT', 3),
]),
)),
('mjtMark',
EnumDecl(
name='mjtMark',
declname='enum mjtMark_',
values=dict([
('mjMARK_NONE', 0),
('mjMARK_EDGE', 1),
('mjMARK_CROSS', 2),
('mjMARK_RANDOM', 3),
]),
)),
('mjtLimited',
EnumDecl(
name='mjtLimited',
declname='enum mjtLimited_',
values=dict([
('mjLIMITED_FALSE', 0),
('mjLIMITED_TRUE', 1),
('mjLIMITED_AUTO', 2),
]),
)),
('mjtAlignFree',
EnumDecl(
name='mjtAlignFree',
declname='enum mjtAlignFree_',
values=dict([
('mjALIGNFREE_FALSE', 0),
('mjALIGNFREE_TRUE', 1),
('mjALIGNFREE_AUTO', 2),
]),
)),
('mjtInertiaFromGeom',
EnumDecl(
name='mjtInertiaFromGeom',
declname='enum mjtInertiaFromGeom_',
values=dict([
('mjINERTIAFROMGEOM_FALSE', 0),
('mjINERTIAFROMGEOM_TRUE', 1),
('mjINERTIAFROMGEOM_AUTO', 2),
]),
)),
('mjtOrientation',
EnumDecl(
name='mjtOrientation',
declname='enum mjtOrientation_',
values=dict([
('mjORIENTATION_QUAT', 0),
('mjORIENTATION_AXISANGLE', 1),
('mjORIENTATION_XYAXES', 2),
('mjORIENTATION_ZAXIS', 3),
('mjORIENTATION_EULER', 4),
]),
)),
('mjtCatBit',
EnumDecl(
name='mjtCatBit',
@@ -774,105 +873,6 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjFONT_BIG', 2),
]),
)),
('mjtGeomInertia',
EnumDecl(
name='mjtGeomInertia',
declname='enum mjtGeomInertia_',
values=dict([
('mjINERTIA_VOLUME', 0),
('mjINERTIA_SHELL', 1),
]),
)),
('mjtMeshInertia',
EnumDecl(
name='mjtMeshInertia',
declname='enum mjtMeshInertia_',
values=dict([
('mjMESH_INERTIA_CONVEX', 0),
('mjMESH_INERTIA_EXACT', 1),
('mjMESH_INERTIA_LEGACY', 2),
('mjMESH_INERTIA_SHELL', 3),
]),
)),
('mjtMeshBuiltin',
EnumDecl(
name='mjtMeshBuiltin',
declname='enum mjtMeshBuiltin_',
values=dict([
('mjMESH_BUILTIN_NONE', 0),
('mjMESH_BUILTIN_SPHERE', 1),
('mjMESH_BUILTIN_HEMISPHERE', 2),
('mjMESH_BUILTIN_CONE', 3),
('mjMESH_BUILTIN_SUPERSPHERE', 4),
('mjMESH_BUILTIN_SUPERTORUS', 5),
('mjMESH_BUILTIN_WEDGE', 6),
('mjMESH_BUILTIN_PLATE', 7),
]),
)),
('mjtBuiltin',
EnumDecl(
name='mjtBuiltin',
declname='enum mjtBuiltin_',
values=dict([
('mjBUILTIN_NONE', 0),
('mjBUILTIN_GRADIENT', 1),
('mjBUILTIN_CHECKER', 2),
('mjBUILTIN_FLAT', 3),
]),
)),
('mjtMark',
EnumDecl(
name='mjtMark',
declname='enum mjtMark_',
values=dict([
('mjMARK_NONE', 0),
('mjMARK_EDGE', 1),
('mjMARK_CROSS', 2),
('mjMARK_RANDOM', 3),
]),
)),
('mjtLimited',
EnumDecl(
name='mjtLimited',
declname='enum mjtLimited_',
values=dict([
('mjLIMITED_FALSE', 0),
('mjLIMITED_TRUE', 1),
('mjLIMITED_AUTO', 2),
]),
)),
('mjtAlignFree',
EnumDecl(
name='mjtAlignFree',
declname='enum mjtAlignFree_',
values=dict([
('mjALIGNFREE_FALSE', 0),
('mjALIGNFREE_TRUE', 1),
('mjALIGNFREE_AUTO', 2),
]),
)),
('mjtInertiaFromGeom',
EnumDecl(
name='mjtInertiaFromGeom',
declname='enum mjtInertiaFromGeom_',
values=dict([
('mjINERTIAFROMGEOM_FALSE', 0),
('mjINERTIAFROMGEOM_TRUE', 1),
('mjINERTIAFROMGEOM_AUTO', 2),
]),
)),
('mjtOrientation',
EnumDecl(
name='mjtOrientation',
declname='enum mjtOrientation_',
values=dict([
('mjORIENTATION_QUAT', 0),
('mjORIENTATION_AXISANGLE', 1),
('mjORIENTATION_XYAXES', 2),
('mjORIENTATION_ZAXIS', 3),
('mjORIENTATION_EULER', 4),
]),
)),
('mjtButton',
EnumDecl(
name='mjtButton',
+90
View File
@@ -301,6 +301,46 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
doc='Parse spec from XML string.',
)),
('mj_parse',
FunctionDecl(
name='mj_parse',
return_type=PointerType(
inner_type=ValueType(name='mjSpec'),
),
parameters=(
FunctionParameterDecl(
name='filename',
type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
),
FunctionParameterDecl(
name='content_type',
type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
),
FunctionParameterDecl(
name='vfs',
type=PointerType(
inner_type=ValueType(name='mjVFS', is_const=True),
),
nullable=True,
),
FunctionParameterDecl(
name='error',
type=PointerType(
inner_type=ValueType(name='char'),
),
nullable=True,
),
FunctionParameterDecl(
name='error_sz',
type=ValueType(name='int'),
),
),
doc='Parse spec from a file.',
)),
('mj_compile',
FunctionDecl(
name='mj_compile',
@@ -9096,6 +9136,56 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
doc='Look up a resource provider by slot number returned by mjp_registerResourceProvider. If invalid slot number, return NULL.', # pylint: disable=line-too-long
)),
('mjp_registerDecoder',
FunctionDecl(
name='mjp_registerDecoder',
return_type=ValueType(name='void'),
parameters=(
FunctionParameterDecl(
name='decoder',
type=PointerType(
inner_type=ValueType(name='mjpDecoder', is_const=True),
),
),
),
doc='Globally register a decoder. This function is thread-safe. If an identical mjpDecoder is already registered, this function does nothing. If a non-identical mjpDecoder with the same name is already registered, an mju_error is raised.', # pylint: disable=line-too-long
)),
('mjp_defaultDecoder',
FunctionDecl(
name='mjp_defaultDecoder',
return_type=ValueType(name='void'),
parameters=(
FunctionParameterDecl(
name='decoder',
type=PointerType(
inner_type=ValueType(name='mjpDecoder'),
),
),
),
doc='Set default resource decoder definition.',
)),
('mjp_findDecoder',
FunctionDecl(
name='mjp_findDecoder',
return_type=PointerType(
inner_type=ValueType(name='mjpDecoder', is_const=True),
),
parameters=(
FunctionParameterDecl(
name='resource',
type=PointerType(
inner_type=ValueType(name='mjResource', is_const=True),
),
),
FunctionParameterDecl(
name='content_type',
type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
),
),
doc='Return the resource provider with the prefix that matches against the resource name. If no match, return NULL.', # pylint: disable=line-too-long
)),
('mju_threadPoolCreate',
FunctionDecl(
name='mju_threadPoolCreate',
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -281,8 +281,8 @@ PYBIND11_MODULE(_specs, m) {
spec = LoadSpecFileImpl(
filename, files,
[&error](const char* filename, const mjVFS* vfs) {
return InterceptMjErrors(mj_parseXML)(
filename, vfs, error, sizeof(error));
return InterceptMjErrors(mj_parse)(
filename, nullptr, vfs, error, sizeof(error));
});
if (!spec) {
throw py::value_error(error);