mujoco-react 8.6.0 → 8.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -43
- package/bin/mujoco-react.mjs +14 -2
- package/dist/index.d.ts +22 -1
- package/dist/index.js.map +1 -1
- package/dist/vite.d.ts +5 -4
- package/dist/vite.js +46 -7
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +10 -0
- package/src/types.ts +27 -0
- package/src/vite.ts +69 -11
package/dist/vite.d.ts
CHANGED
|
@@ -12,8 +12,8 @@ type ViteServer = {
|
|
|
12
12
|
};
|
|
13
13
|
};
|
|
14
14
|
interface MujocoReactPluginOptions {
|
|
15
|
-
/** Entry MJCF/URDF files to scan. */
|
|
16
|
-
models:
|
|
15
|
+
/** Entry MJCF/URDF files to scan. Prefer a record for stable per-robot type names. */
|
|
16
|
+
models: ModelInput;
|
|
17
17
|
/** Generated declaration file. Defaults to `src/mujoco-register.gen.d.ts`. */
|
|
18
18
|
generatedRegister?: string;
|
|
19
19
|
/** Module name to augment. Defaults to `mujoco-react`. */
|
|
@@ -22,7 +22,7 @@ interface MujocoReactPluginOptions {
|
|
|
22
22
|
disableLogging?: boolean;
|
|
23
23
|
}
|
|
24
24
|
interface MujocoRegisterCodegenOptions {
|
|
25
|
-
models:
|
|
25
|
+
models: ModelInput;
|
|
26
26
|
out: string;
|
|
27
27
|
moduleName?: string;
|
|
28
28
|
root?: string;
|
|
@@ -33,6 +33,7 @@ interface MujocoRegisterCodegenResult {
|
|
|
33
33
|
counts: Record<RegisterKey, number>;
|
|
34
34
|
}
|
|
35
35
|
type RegisterKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes';
|
|
36
|
+
type ModelInput = string | readonly string[] | Record<string, string>;
|
|
36
37
|
declare function mujocoReact(options: MujocoReactPluginOptions): {
|
|
37
38
|
name: string;
|
|
38
39
|
enforce: "pre";
|
|
@@ -44,4 +45,4 @@ declare function mujocoReact(options: MujocoReactPluginOptions): {
|
|
|
44
45
|
};
|
|
45
46
|
declare function generateMujocoRegister(options: MujocoRegisterCodegenOptions): Promise<MujocoRegisterCodegenResult>;
|
|
46
47
|
|
|
47
|
-
export { type MujocoReactPluginOptions, type MujocoRegisterCodegenOptions, type MujocoRegisterCodegenResult, generateMujocoRegister, mujocoReact };
|
|
48
|
+
export { type ModelInput, type MujocoReactPluginOptions, type MujocoRegisterCodegenOptions, type MujocoRegisterCodegenResult, generateMujocoRegister, mujocoReact };
|
package/dist/vite.js
CHANGED
|
@@ -16,13 +16,13 @@ function createEmptyNames() {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
function mujocoReact(options) {
|
|
19
|
-
const models =
|
|
19
|
+
const models = normalizeModels(options.models);
|
|
20
20
|
let root = process.cwd();
|
|
21
21
|
let generatedRegister = options.generatedRegister ?? "src/mujoco-register.gen.d.ts";
|
|
22
22
|
let watchedFiles = [];
|
|
23
23
|
async function generate() {
|
|
24
24
|
const result = await generateMujocoRegister({
|
|
25
|
-
models,
|
|
25
|
+
models: options.models,
|
|
26
26
|
out: generatedRegister,
|
|
27
27
|
moduleName: options.moduleName,
|
|
28
28
|
root
|
|
@@ -65,13 +65,15 @@ async function generateMujocoRegister(options) {
|
|
|
65
65
|
const root = path.resolve(options.root ?? process.cwd());
|
|
66
66
|
const out = path.resolve(root, options.out);
|
|
67
67
|
const moduleName = options.moduleName ?? "mujoco-react";
|
|
68
|
+
const models = normalizeModels(options.models);
|
|
68
69
|
const names = createEmptyNames();
|
|
69
70
|
const seen = /* @__PURE__ */ new Set();
|
|
70
|
-
for (const model of
|
|
71
|
-
await scanModel(path.resolve(root, model), root, seen, names);
|
|
71
|
+
for (const model of models) {
|
|
72
|
+
await scanModel(path.resolve(root, model.file), root, seen, model.names);
|
|
73
|
+
mergeNames(names, model.names);
|
|
72
74
|
}
|
|
73
75
|
await mkdir(path.dirname(out), { recursive: true });
|
|
74
|
-
await writeFile(out, renderRegister(moduleName, names), "utf8");
|
|
76
|
+
await writeFile(out, renderRegister(moduleName, names, models), "utf8");
|
|
75
77
|
return {
|
|
76
78
|
out,
|
|
77
79
|
files: [...seen].sort((a, b) => a.localeCompare(b)),
|
|
@@ -125,8 +127,11 @@ function readAttr(attrs, attr) {
|
|
|
125
127
|
const pattern = new RegExp(`\\b${attr}\\s*=\\s*(['"])(.*?)\\1`, "i");
|
|
126
128
|
return attrs.match(pattern)?.[2];
|
|
127
129
|
}
|
|
128
|
-
function renderRegister(moduleName, names) {
|
|
130
|
+
function renderRegister(moduleName, names, models) {
|
|
129
131
|
const fields = REGISTER_KEYS.filter((key) => names[key].size > 0).map((key) => ` ${key}: ${renderUnion(names[key])};`);
|
|
132
|
+
const robots = models.map((model) => ` ${quoteProperty(model.id)}: {
|
|
133
|
+
${renderRobotFields(model.names)}
|
|
134
|
+
};`);
|
|
130
135
|
return `// Auto-generated by mujoco-react. Do not edit.
|
|
131
136
|
// Regenerate by running Vite with the mujocoReact() plugin or \`mujoco-react codegen\`.
|
|
132
137
|
|
|
@@ -134,22 +139,56 @@ import 'mujoco-react';
|
|
|
134
139
|
|
|
135
140
|
declare module '${moduleName}' {
|
|
136
141
|
interface Register {
|
|
142
|
+
robots: {
|
|
143
|
+
${robots.join("\n")}
|
|
144
|
+
};
|
|
137
145
|
${fields.join("\n")}
|
|
138
146
|
}
|
|
139
147
|
}
|
|
140
148
|
`;
|
|
141
149
|
}
|
|
150
|
+
function renderRobotFields(names) {
|
|
151
|
+
return REGISTER_KEYS.map((key) => ` ${key}: ${names[key].size > 0 ? renderUnion(names[key]) : "never"};`).join("\n");
|
|
152
|
+
}
|
|
142
153
|
function renderUnion(values) {
|
|
143
154
|
return [...values].sort((a, b) => a.localeCompare(b)).map((value) => `'${escapeTs(value)}'`).join(" | ");
|
|
144
155
|
}
|
|
145
156
|
function escapeTs(value) {
|
|
146
157
|
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
147
158
|
}
|
|
159
|
+
function quoteProperty(value) {
|
|
160
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : `'${escapeTs(value)}'`;
|
|
161
|
+
}
|
|
162
|
+
function normalizeModels(input) {
|
|
163
|
+
if (typeof input === "string") return [createModelEntry(deriveModelId(input), input)];
|
|
164
|
+
if (Array.isArray(input)) return input.map((file) => createModelEntry(deriveModelId(file), file));
|
|
165
|
+
return Object.entries(input).map(([id, file]) => createModelEntry(sanitizeModelId(id), file));
|
|
166
|
+
}
|
|
167
|
+
function createModelEntry(id, file) {
|
|
168
|
+
return { id, file, names: createEmptyNames() };
|
|
169
|
+
}
|
|
170
|
+
function deriveModelId(file) {
|
|
171
|
+
const normalized = file.replace(/\\/g, "/");
|
|
172
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
173
|
+
const filename = parts.at(-1) ?? "model";
|
|
174
|
+
const parent = parts.length > 1 ? parts.at(-2) : void 0;
|
|
175
|
+
const base = filename.replace(/\.(xml|mjcf|urdf)$/i, "");
|
|
176
|
+
return sanitizeModelId(parent && ["scene", "model", "robot"].includes(base.toLowerCase()) ? parent : base);
|
|
177
|
+
}
|
|
178
|
+
function sanitizeModelId(value) {
|
|
179
|
+
const sanitized = value.replace(/[^A-Za-z0-9_$]/g, "_").replace(/^[^A-Za-z_$]+/, "");
|
|
180
|
+
return sanitized || "model";
|
|
181
|
+
}
|
|
182
|
+
function mergeNames(target, source) {
|
|
183
|
+
for (const key of REGISTER_KEYS) {
|
|
184
|
+
for (const value of source[key]) target[key].add(value);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
148
187
|
function shouldRegenerate(file, watchedFiles, models, root) {
|
|
149
188
|
const absolute = path.resolve(file);
|
|
150
189
|
if (watchedFiles.includes(absolute)) return true;
|
|
151
190
|
if (!MODEL_EXTENSIONS.has(path.extname(absolute).toLowerCase())) return false;
|
|
152
|
-
const modelDirs = models.map((model) => path.dirname(path.resolve(root, model)));
|
|
191
|
+
const modelDirs = models.map((model) => path.dirname(path.resolve(root, model.file)));
|
|
153
192
|
return modelDirs.some((dir) => absolute.startsWith(dir));
|
|
154
193
|
}
|
|
155
194
|
/**
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vite.ts"],"names":[],"mappings":"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","file":"vite.js","sourcesContent":["/**\n * @license\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\ntype ViteConfig = { root: string };\ntype ViteServer = {\n watcher: {\n add(paths: string | string[]): void;\n on(event: 'add' | 'change' | 'unlink', listener: (file: string) => void): void;\n };\n};\n\nexport interface MujocoReactPluginOptions {\n /** Entry MJCF/URDF files to scan. */\n models: string | readonly string[];\n /** Generated declaration file. Defaults to `src/mujoco-register.gen.d.ts`. */\n generatedRegister?: string;\n /** Module name to augment. Defaults to `mujoco-react`. */\n moduleName?: string;\n /** Disable console output. */\n disableLogging?: boolean;\n}\n\nexport interface MujocoRegisterCodegenOptions {\n models: readonly string[];\n out: string;\n moduleName?: string;\n root?: string;\n}\n\nexport interface MujocoRegisterCodegenResult {\n out: string;\n files: string[];\n counts: Record<RegisterKey, number>;\n}\n\ntype RegisterKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes';\n\nconst REGISTER_KEYS: RegisterKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes'];\nconst MODEL_EXTENSIONS = new Set(['.xml', '.mjcf', '.urdf']);\n\nfunction createEmptyNames(): Record<RegisterKey, Set<string>> {\n return {\n actuators: new Set(),\n sensors: new Set(),\n bodies: new Set(),\n joints: new Set(),\n sites: new Set(),\n geoms: new Set(),\n keyframes: new Set(),\n };\n}\n\nexport function mujocoReact(options: MujocoReactPluginOptions) {\n const models = Array.isArray(options.models) ? options.models : [options.models];\n let root = process.cwd();\n let generatedRegister = options.generatedRegister ?? 'src/mujoco-register.gen.d.ts';\n let watchedFiles: string[] = [];\n\n async function generate() {\n const result = await generateMujocoRegister({\n models,\n out: generatedRegister,\n moduleName: options.moduleName,\n root,\n });\n watchedFiles = result.files;\n if (!options.disableLogging) {\n const total = Object.values(result.counts).reduce((sum, count) => sum + count, 0);\n console.log(`[mujoco-react] generated ${path.relative(root, result.out)} (${total} names)`);\n }\n return result;\n }\n\n return {\n name: 'mujoco-react',\n enforce: 'pre' as const,\n configResolved(config: ViteConfig) {\n root = config.root;\n generatedRegister = path.resolve(root, generatedRegister);\n },\n async buildStart(this: { addWatchFile?: (file: string) => void }) {\n const result = await generate();\n for (const file of result.files) this.addWatchFile?.(file);\n },\n configureServer(server: ViteServer) {\n generate().then((result) => server.watcher.add(result.files)).catch((error: unknown) => {\n console.error('[mujoco-react] register generation failed', error);\n });\n\n server.watcher.on('add', regenerateIfModelFile);\n server.watcher.on('change', regenerateIfModelFile);\n server.watcher.on('unlink', regenerateIfModelFile);\n\n function regenerateIfModelFile(file: string) {\n if (!shouldRegenerate(file, watchedFiles, models, root)) return;\n generate().then((result) => server.watcher.add(result.files)).catch((error: unknown) => {\n console.error('[mujoco-react] register generation failed', error);\n });\n }\n },\n };\n}\n\nexport async function generateMujocoRegister(\n options: MujocoRegisterCodegenOptions\n): Promise<MujocoRegisterCodegenResult> {\n const root = path.resolve(options.root ?? process.cwd());\n const out = path.resolve(root, options.out);\n const moduleName = options.moduleName ?? 'mujoco-react';\n const names = createEmptyNames();\n const seen = new Set<string>();\n\n for (const model of options.models) {\n await scanModel(path.resolve(root, model), root, seen, names);\n }\n\n await mkdir(path.dirname(out), { recursive: true });\n await writeFile(out, renderRegister(moduleName, names), 'utf8');\n\n return {\n out,\n files: [...seen].sort((a, b) => a.localeCompare(b)),\n counts: Object.fromEntries(REGISTER_KEYS.map((key) => [key, names[key].size])) as Record<RegisterKey, number>,\n };\n}\n\nasync function scanModel(\n filePath: string,\n root: string,\n seen: Set<string>,\n names: Record<RegisterKey, Set<string>>\n) {\n const normalized = path.normalize(filePath);\n if (seen.has(normalized)) return;\n seen.add(normalized);\n\n const xml = await readFile(normalized, 'utf8');\n collectSimpleTagNames(xml, 'body', names.bodies);\n collectSimpleTagNames(xml, 'joint', names.joints);\n collectSimpleTagNames(xml, 'site', names.sites);\n collectSimpleTagNames(xml, 'geom', names.geoms);\n collectSimpleTagNames(xml, 'key', names.keyframes);\n collectSectionNames(xml, 'actuator', names.actuators);\n collectSectionNames(xml, 'sensor', names.sensors);\n\n for (const includePath of collectIncludePaths(xml)) {\n const next = path.resolve(path.dirname(normalized), includePath);\n if (next.startsWith(root)) await scanModel(next, root, seen, names);\n }\n}\n\nfunction collectSimpleTagNames(xml: string, tag: string, target: Set<string>) {\n const pattern = new RegExp(`<\\\\s*${tag}\\\\b([^>]*)>`, 'gi');\n for (const match of xml.matchAll(pattern)) {\n const name = readAttr(match[1], 'name');\n if (name) target.add(name);\n }\n}\n\nfunction collectSectionNames(xml: string, section: string, target: Set<string>) {\n const sectionPattern = new RegExp(`<\\\\s*${section}\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\s*/\\\\s*${section}\\\\s*>`, 'gi');\n for (const sectionMatch of xml.matchAll(sectionPattern)) {\n const tagPattern = /<\\s*[a-zA-Z0-9_:-]+\\b([^>]*)>/g;\n for (const tagMatch of sectionMatch[1].matchAll(tagPattern)) {\n const name = readAttr(tagMatch[1], 'name');\n if (name) target.add(name);\n }\n }\n}\n\nfunction collectIncludePaths(xml: string): string[] {\n const result: string[] = [];\n const pattern = /<\\s*include\\b([^>]*)>/gi;\n for (const match of xml.matchAll(pattern)) {\n const file = readAttr(match[1], 'file');\n if (file && !file.includes('://') && !file.startsWith('/')) result.push(file);\n }\n return result;\n}\n\nfunction readAttr(attrs: string, attr: string): string | undefined {\n const pattern = new RegExp(`\\\\b${attr}\\\\s*=\\\\s*(['\"])(.*?)\\\\1`, 'i');\n return attrs.match(pattern)?.[2];\n}\n\nfunction renderRegister(moduleName: string, names: Record<RegisterKey, Set<string>>): string {\n const fields = REGISTER_KEYS\n .filter((key) => names[key].size > 0)\n .map((key) => ` ${key}: ${renderUnion(names[key])};`);\n\n return `// Auto-generated by mujoco-react. Do not edit.\n// Regenerate by running Vite with the mujocoReact() plugin or \\`mujoco-react codegen\\`.\n\nimport 'mujoco-react';\n\ndeclare module '${moduleName}' {\n interface Register {\n${fields.join('\\n')}\n }\n}\n`;\n}\n\nfunction renderUnion(values: Set<string>): string {\n return [...values].sort((a, b) => a.localeCompare(b)).map((value) => `'${escapeTs(value)}'`).join(' | ');\n}\n\nfunction escapeTs(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nfunction shouldRegenerate(file: string, watchedFiles: string[], models: readonly string[], root: string): boolean {\n const absolute = path.resolve(file);\n if (watchedFiles.includes(absolute)) return true;\n if (!MODEL_EXTENSIONS.has(path.extname(absolute).toLowerCase())) return false;\n const modelDirs = models.map((model) => path.dirname(path.resolve(root, model)));\n return modelDirs.some((dir) => absolute.startsWith(dir));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/vite.ts"],"names":[],"mappings":";;;;AAiDA,IAAM,aAAA,GAA+B,CAAC,WAAA,EAAa,SAAA,EAAW,UAAU,QAAA,EAAU,OAAA,EAAS,SAAS,WAAW,CAAA;AAC/G,IAAM,mCAAmB,IAAI,GAAA,CAAI,CAAC,MAAA,EAAQ,OAAA,EAAS,OAAO,CAAC,CAAA;AAE3D,SAAS,gBAAA,GAAqD;AAC5D,EAAA,OAAO;AAAA,IACL,SAAA,sBAAe,GAAA,EAAI;AAAA,IACnB,OAAA,sBAAa,GAAA,EAAI;AAAA,IACjB,MAAA,sBAAY,GAAA,EAAI;AAAA,IAChB,MAAA,sBAAY,GAAA,EAAI;AAAA,IAChB,KAAA,sBAAW,GAAA,EAAI;AAAA,IACf,KAAA,sBAAW,GAAA,EAAI;AAAA,IACf,SAAA,sBAAe,GAAA;AAAI,GACrB;AACF;AAEO,SAAS,YAAY,OAAA,EAAmC;AAC7D,EAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,OAAA,CAAQ,MAAM,CAAA;AAC7C,EAAA,IAAI,IAAA,GAAO,QAAQ,GAAA,EAAI;AACvB,EAAA,IAAI,iBAAA,GAAoB,QAAQ,iBAAA,IAAqB,8BAAA;AACrD,EAAA,IAAI,eAAyB,EAAC;AAE9B,EAAA,eAAe,QAAA,GAAW;AACxB,IAAA,MAAM,MAAA,GAAS,MAAM,sBAAA,CAAuB;AAAA,MAC1C,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,GAAA,EAAK,iBAAA;AAAA,MACL,YAAY,OAAA,CAAQ,UAAA;AAAA,MACpB;AAAA,KACD,CAAA;AACD,IAAA,YAAA,GAAe,MAAA,CAAO,KAAA;AACtB,IAAA,IAAI,CAAC,QAAQ,cAAA,EAAgB;AAC3B,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA,CAAE,MAAA,CAAO,CAAC,GAAA,EAAK,KAAA,KAAU,GAAA,GAAM,KAAA,EAAO,CAAC,CAAA;AAChF,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,yBAAA,EAA4B,IAAA,CAAK,QAAA,CAAS,IAAA,EAAM,OAAO,GAAG,CAAC,CAAA,EAAA,EAAK,KAAK,CAAA,OAAA,CAAS,CAAA;AAAA,IAC5F;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,cAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,eAAe,MAAA,EAAoB;AACjC,MAAA,IAAA,GAAO,MAAA,CAAO,IAAA;AACd,MAAA,iBAAA,GAAoB,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,iBAAiB,CAAA;AAAA,IAC1D,CAAA;AAAA,IACA,MAAM,UAAA,GAA4D;AAChE,MAAA,MAAM,MAAA,GAAS,MAAM,QAAA,EAAS;AAC9B,MAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,KAAA,EAAO,IAAA,CAAK,eAAe,IAAI,CAAA;AAAA,IAC3D,CAAA;AAAA,IACA,gBAAgB,MAAA,EAAoB;AAClC,MAAA,QAAA,EAAS,CAAE,IAAA,CAAK,CAAC,MAAA,KAAW,MAAA,CAAO,OAAA,CAAQ,GAAA,CAAI,MAAA,CAAO,KAAK,CAAC,CAAA,CAAE,KAAA,CAAM,CAAC,KAAA,KAAmB;AACtF,QAAA,OAAA,CAAQ,KAAA,CAAM,6CAA6C,KAAK,CAAA;AAAA,MAClE,CAAC,CAAA;AAED,MAAA,MAAA,CAAO,OAAA,CAAQ,EAAA,CAAG,KAAA,EAAO,qBAAqB,CAAA;AAC9C,MAAA,MAAA,CAAO,OAAA,CAAQ,EAAA,CAAG,QAAA,EAAU,qBAAqB,CAAA;AACjD,MAAA,MAAA,CAAO,OAAA,CAAQ,EAAA,CAAG,QAAA,EAAU,qBAAqB,CAAA;AAEjD,MAAA,SAAS,sBAAsB,IAAA,EAAc;AAC3C,QAAA,IAAI,CAAC,gBAAA,CAAiB,IAAA,EAAM,YAAA,EAAc,MAAA,EAAQ,IAAI,CAAA,EAAG;AACzD,QAAA,QAAA,EAAS,CAAE,IAAA,CAAK,CAAC,MAAA,KAAW,MAAA,CAAO,OAAA,CAAQ,GAAA,CAAI,MAAA,CAAO,KAAK,CAAC,CAAA,CAAE,KAAA,CAAM,CAAC,KAAA,KAAmB;AACtF,UAAA,OAAA,CAAQ,KAAA,CAAM,6CAA6C,KAAK,CAAA;AAAA,QAClE,CAAC,CAAA;AAAA,MACH;AAAA,IACF;AAAA,GACF;AACF;AAEA,eAAsB,uBACpB,OAAA,EACsC;AACtC,EAAA,MAAM,OAAO,IAAA,CAAK,OAAA,CAAQ,QAAQ,IAAA,IAAQ,OAAA,CAAQ,KAAK,CAAA;AACvD,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,QAAQ,GAAG,CAAA;AAC1C,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,cAAA;AACzC,EAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,OAAA,CAAQ,MAAM,CAAA;AAC7C,EAAA,MAAM,QAAQ,gBAAA,EAAiB;AAC/B,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAE7B,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,MAAM,SAAA,CAAU,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,KAAA,CAAM,IAAI,CAAA,EAAG,IAAA,EAAM,IAAA,EAAM,KAAA,CAAM,KAAK,CAAA;AACvE,IAAA,UAAA,CAAW,KAAA,EAAO,MAAM,KAAK,CAAA;AAAA,EAC/B;AAEA,EAAA,MAAM,KAAA,CAAM,KAAK,OAAA,CAAQ,GAAG,GAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AAClD,EAAA,MAAM,UAAU,GAAA,EAAK,cAAA,CAAe,YAAY,KAAA,EAAO,MAAM,GAAG,MAAM,CAAA;AAEtE,EAAA,OAAO;AAAA,IACL,GAAA;AAAA,IACA,KAAA,EAAO,CAAC,GAAG,IAAI,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,aAAA,CAAc,CAAC,CAAC,CAAA;AAAA,IAClD,MAAA,EAAQ,MAAA,CAAO,WAAA,CAAY,aAAA,CAAc,IAAI,CAAC,GAAA,KAAQ,CAAC,GAAA,EAAK,KAAA,CAAM,GAAG,CAAA,CAAE,IAAI,CAAC,CAAC;AAAA,GAC/E;AACF;AAEA,eAAe,SAAA,CACb,QAAA,EACA,IAAA,EACA,IAAA,EACA,KAAA,EACA;AACA,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,SAAA,CAAU,QAAQ,CAAA;AAC1C,EAAA,IAAI,IAAA,CAAK,GAAA,CAAI,UAAU,CAAA,EAAG;AAC1B,EAAA,IAAA,CAAK,IAAI,UAAU,CAAA;AAEnB,EAAA,MAAM,GAAA,GAAM,MAAM,QAAA,CAAS,UAAA,EAAY,MAAM,CAAA;AAC7C,EAAA,qBAAA,CAAsB,GAAA,EAAK,MAAA,EAAQ,KAAA,CAAM,MAAM,CAAA;AAC/C,EAAA,qBAAA,CAAsB,GAAA,EAAK,OAAA,EAAS,KAAA,CAAM,MAAM,CAAA;AAChD,EAAA,qBAAA,CAAsB,GAAA,EAAK,MAAA,EAAQ,KAAA,CAAM,KAAK,CAAA;AAC9C,EAAA,qBAAA,CAAsB,GAAA,EAAK,MAAA,EAAQ,KAAA,CAAM,KAAK,CAAA;AAC9C,EAAA,qBAAA,CAAsB,GAAA,EAAK,KAAA,EAAO,KAAA,CAAM,SAAS,CAAA;AACjD,EAAA,mBAAA,CAAoB,GAAA,EAAK,UAAA,EAAY,KAAA,CAAM,SAAS,CAAA;AACpD,EAAA,mBAAA,CAAoB,GAAA,EAAK,QAAA,EAAU,KAAA,CAAM,OAAO,CAAA;AAEhD,EAAA,KAAA,MAAW,WAAA,IAAe,mBAAA,CAAoB,GAAG,CAAA,EAAG;AAClD,IAAA,MAAM,OAAO,IAAA,CAAK,OAAA,CAAQ,KAAK,OAAA,CAAQ,UAAU,GAAG,WAAW,CAAA;AAC/D,IAAA,IAAI,IAAA,CAAK,WAAW,IAAI,CAAA,QAAS,SAAA,CAAU,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,KAAK,CAAA;AAAA,EACpE;AACF;AAEA,SAAS,qBAAA,CAAsB,GAAA,EAAa,GAAA,EAAa,MAAA,EAAqB;AAC5E,EAAA,MAAM,UAAU,IAAI,MAAA,CAAO,CAAA,KAAA,EAAQ,GAAG,eAAe,IAAI,CAAA;AACzD,EAAA,KAAA,MAAW,KAAA,IAAS,GAAA,CAAI,QAAA,CAAS,OAAO,CAAA,EAAG;AACzC,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,KAAA,CAAM,CAAC,GAAG,MAAM,CAAA;AACtC,IAAA,IAAI,IAAA,EAAM,MAAA,CAAO,GAAA,CAAI,IAAI,CAAA;AAAA,EAC3B;AACF;AAEA,SAAS,mBAAA,CAAoB,GAAA,EAAa,OAAA,EAAiB,MAAA,EAAqB;AAC9E,EAAA,MAAM,cAAA,GAAiB,IAAI,MAAA,CAAO,CAAA,KAAA,EAAQ,OAAO,CAAA,+BAAA,EAAkC,OAAO,SAAS,IAAI,CAAA;AACvG,EAAA,KAAA,MAAW,YAAA,IAAgB,GAAA,CAAI,QAAA,CAAS,cAAc,CAAA,EAAG;AACvD,IAAA,MAAM,UAAA,GAAa,gCAAA;AACnB,IAAA,KAAA,MAAW,YAAY,YAAA,CAAa,CAAC,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG;AAC3D,MAAA,MAAM,IAAA,GAAO,QAAA,CAAS,QAAA,CAAS,CAAC,GAAG,MAAM,CAAA;AACzC,MAAA,IAAI,IAAA,EAAM,MAAA,CAAO,GAAA,CAAI,IAAI,CAAA;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,SAAS,oBAAoB,GAAA,EAAuB;AAClD,EAAA,MAAM,SAAmB,EAAC;AAC1B,EAAA,MAAM,OAAA,GAAU,yBAAA;AAChB,EAAA,KAAA,MAAW,KAAA,IAAS,GAAA,CAAI,QAAA,CAAS,OAAO,CAAA,EAAG;AACzC,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,KAAA,CAAM,CAAC,GAAG,MAAM,CAAA;AACtC,IAAA,IAAI,IAAA,IAAQ,CAAC,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA,IAAK,CAAC,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG,MAAA,CAAO,KAAK,IAAI,CAAA;AAAA,EAC9E;AACA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,QAAA,CAAS,OAAe,IAAA,EAAkC;AACjE,EAAA,MAAM,UAAU,IAAI,MAAA,CAAO,CAAA,GAAA,EAAM,IAAI,2BAA2B,GAAG,CAAA;AACnE,EAAA,OAAO,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA,GAAI,CAAC,CAAA;AACjC;AAEA,SAAS,cAAA,CACP,UAAA,EACA,KAAA,EACA,MAAA,EACQ;AACR,EAAA,MAAM,MAAA,GAAS,cACZ,MAAA,CAAO,CAAC,QAAQ,KAAA,CAAM,GAAG,CAAA,CAAE,IAAA,GAAO,CAAC,CAAA,CACnC,IAAI,CAAC,GAAA,KAAQ,OAAO,GAAG,CAAA,EAAA,EAAK,YAAY,KAAA,CAAM,GAAG,CAAC,CAAC,CAAA,CAAA,CAAG,CAAA;AACzD,EAAA,MAAM,MAAA,GAAS,OACZ,GAAA,CAAI,CAAC,UAAU,CAAA,MAAA,EAAS,aAAA,CAAc,KAAA,CAAM,EAAE,CAAC,CAAA;AAAA,EAAQ,iBAAA,CAAkB,KAAA,CAAM,KAAK,CAAC;AAAA,QAAA,CAAY,CAAA;AAEpG,EAAA,OAAO,CAAA;AAAA;;AAAA;;AAAA,gBAAA,EAKS,UAAU,CAAA;AAAA;AAAA;AAAA,EAG1B,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC;AAAA;AAAA,EAEjB,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC;AAAA;AAAA;AAAA,CAAA;AAInB;AAEA,SAAS,kBAAkB,KAAA,EAAiD;AAC1E,EAAA,OAAO,aAAA,CACJ,IAAI,CAAC,GAAA,KAAQ,WAAW,GAAG,CAAA,EAAA,EAAK,MAAM,GAAG,CAAA,CAAE,OAAO,CAAA,GAAI,WAAA,CAAY,MAAM,GAAG,CAAC,IAAI,OAAO,CAAA,CAAA,CAAG,CAAA,CAC1F,IAAA,CAAK,IAAI,CAAA;AACd;AAEA,SAAS,YAAY,MAAA,EAA6B;AAChD,EAAA,OAAO,CAAC,GAAG,MAAM,CAAA,CAAE,KAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,aAAA,CAAc,CAAC,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,KAAA,KAAU,CAAA,CAAA,EAAI,QAAA,CAAS,KAAK,CAAC,CAAA,CAAA,CAAG,CAAA,CAAE,IAAA,CAAK,KAAK,CAAA;AACzG;AAEA,SAAS,SAAS,KAAA,EAAuB;AACvC,EAAA,OAAO,MAAM,OAAA,CAAQ,KAAA,EAAO,MAAM,CAAA,CAAE,OAAA,CAAQ,MAAM,KAAK,CAAA;AACzD;AAEA,SAAS,cAAc,KAAA,EAAuB;AAC5C,EAAA,OAAO,4BAAA,CAA6B,KAAK,KAAK,CAAA,GAAI,QAAQ,CAAA,CAAA,EAAI,QAAA,CAAS,KAAK,CAAC,CAAA,CAAA,CAAA;AAC/E;AAEA,SAAS,gBAAgB,KAAA,EAAiC;AACxD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,CAAC,iBAAiB,aAAA,CAAc,KAAK,CAAA,EAAG,KAAK,CAAC,CAAA;AACpF,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,SAAU,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,KAAS,gBAAA,CAAiB,aAAA,CAAc,IAAI,CAAA,EAAG,IAAI,CAAC,CAAA;AAChG,EAAA,OAAO,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,CAAE,IAAI,CAAC,CAAC,EAAA,EAAI,IAAI,MAAM,gBAAA,CAAiB,eAAA,CAAgB,EAAE,CAAA,EAAG,IAAI,CAAC,CAAA;AAC9F;AAEA,SAAS,gBAAA,CAAiB,IAAY,IAAA,EAA0B;AAC9D,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,kBAAiB,EAAE;AAC/C;AAEA,SAAS,cAAc,IAAA,EAAsB;AAC3C,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA;AAC1C,EAAA,MAAM,QAAQ,UAAA,CAAW,KAAA,CAAM,GAAG,CAAA,CAAE,OAAO,OAAO,CAAA;AAClD,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,IAAK,OAAA;AACjC,EAAA,MAAM,SAAS,KAAA,CAAM,MAAA,GAAS,IAAI,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,GAAI,MAAA;AACjD,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,OAAA,CAAQ,qBAAA,EAAuB,EAAE,CAAA;AACvD,EAAA,OAAO,eAAA,CAAgB,MAAA,IAAU,CAAC,OAAA,EAAS,OAAA,EAAS,OAAO,CAAA,CAAE,QAAA,CAAS,IAAA,CAAK,WAAA,EAAa,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3G;AAEA,SAAS,gBAAgB,KAAA,EAAuB;AAC9C,EAAA,MAAM,SAAA,GAAY,MAAM,OAAA,CAAQ,iBAAA,EAAmB,GAAG,CAAA,CAAE,OAAA,CAAQ,iBAAiB,EAAE,CAAA;AACnF,EAAA,OAAO,SAAA,IAAa,OAAA;AACtB;AAEA,SAAS,UAAA,CAAW,QAA0C,MAAA,EAA0C;AACtG,EAAA,KAAA,MAAW,OAAO,aAAA,EAAe;AAC/B,IAAA,KAAA,MAAW,KAAA,IAAS,OAAO,GAAG,CAAA,SAAU,GAAG,CAAA,CAAE,IAAI,KAAK,CAAA;AAAA,EACxD;AACF;AAEA,SAAS,gBAAA,CAAiB,IAAA,EAAc,YAAA,EAAwB,MAAA,EAA+B,IAAA,EAAuB;AACpH,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA;AAClC,EAAA,IAAI,YAAA,CAAa,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,IAAA;AAC5C,EAAA,IAAI,CAAC,gBAAA,CAAiB,GAAA,CAAI,IAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA,CAAE,WAAA,EAAa,CAAA,EAAG,OAAO,KAAA;AACxE,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,KAAA,CAAM,IAAI,CAAC,CAAC,CAAA;AACpF,EAAA,OAAO,UAAU,IAAA,CAAK,CAAC,QAAQ,QAAA,CAAS,UAAA,CAAW,GAAG,CAAC,CAAA;AACzD","file":"vite.js","sourcesContent":["/**\n * @license\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\ntype ViteConfig = { root: string };\ntype ViteServer = {\n watcher: {\n add(paths: string | string[]): void;\n on(event: 'add' | 'change' | 'unlink', listener: (file: string) => void): void;\n };\n};\n\nexport interface MujocoReactPluginOptions {\n /** Entry MJCF/URDF files to scan. Prefer a record for stable per-robot type names. */\n models: ModelInput;\n /** Generated declaration file. Defaults to `src/mujoco-register.gen.d.ts`. */\n generatedRegister?: string;\n /** Module name to augment. Defaults to `mujoco-react`. */\n moduleName?: string;\n /** Disable console output. */\n disableLogging?: boolean;\n}\n\nexport interface MujocoRegisterCodegenOptions {\n models: ModelInput;\n out: string;\n moduleName?: string;\n root?: string;\n}\n\nexport interface MujocoRegisterCodegenResult {\n out: string;\n files: string[];\n counts: Record<RegisterKey, number>;\n}\n\ntype RegisterKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes';\nexport type ModelInput = string | readonly string[] | Record<string, string>;\n\ninterface ModelEntry {\n id: string;\n file: string;\n names: Record<RegisterKey, Set<string>>;\n}\n\nconst REGISTER_KEYS: RegisterKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes'];\nconst MODEL_EXTENSIONS = new Set(['.xml', '.mjcf', '.urdf']);\n\nfunction createEmptyNames(): Record<RegisterKey, Set<string>> {\n return {\n actuators: new Set(),\n sensors: new Set(),\n bodies: new Set(),\n joints: new Set(),\n sites: new Set(),\n geoms: new Set(),\n keyframes: new Set(),\n };\n}\n\nexport function mujocoReact(options: MujocoReactPluginOptions) {\n const models = normalizeModels(options.models);\n let root = process.cwd();\n let generatedRegister = options.generatedRegister ?? 'src/mujoco-register.gen.d.ts';\n let watchedFiles: string[] = [];\n\n async function generate() {\n const result = await generateMujocoRegister({\n models: options.models,\n out: generatedRegister,\n moduleName: options.moduleName,\n root,\n });\n watchedFiles = result.files;\n if (!options.disableLogging) {\n const total = Object.values(result.counts).reduce((sum, count) => sum + count, 0);\n console.log(`[mujoco-react] generated ${path.relative(root, result.out)} (${total} names)`);\n }\n return result;\n }\n\n return {\n name: 'mujoco-react',\n enforce: 'pre' as const,\n configResolved(config: ViteConfig) {\n root = config.root;\n generatedRegister = path.resolve(root, generatedRegister);\n },\n async buildStart(this: { addWatchFile?: (file: string) => void }) {\n const result = await generate();\n for (const file of result.files) this.addWatchFile?.(file);\n },\n configureServer(server: ViteServer) {\n generate().then((result) => server.watcher.add(result.files)).catch((error: unknown) => {\n console.error('[mujoco-react] register generation failed', error);\n });\n\n server.watcher.on('add', regenerateIfModelFile);\n server.watcher.on('change', regenerateIfModelFile);\n server.watcher.on('unlink', regenerateIfModelFile);\n\n function regenerateIfModelFile(file: string) {\n if (!shouldRegenerate(file, watchedFiles, models, root)) return;\n generate().then((result) => server.watcher.add(result.files)).catch((error: unknown) => {\n console.error('[mujoco-react] register generation failed', error);\n });\n }\n },\n };\n}\n\nexport async function generateMujocoRegister(\n options: MujocoRegisterCodegenOptions\n): Promise<MujocoRegisterCodegenResult> {\n const root = path.resolve(options.root ?? process.cwd());\n const out = path.resolve(root, options.out);\n const moduleName = options.moduleName ?? 'mujoco-react';\n const models = normalizeModels(options.models);\n const names = createEmptyNames();\n const seen = new Set<string>();\n\n for (const model of models) {\n await scanModel(path.resolve(root, model.file), root, seen, model.names);\n mergeNames(names, model.names);\n }\n\n await mkdir(path.dirname(out), { recursive: true });\n await writeFile(out, renderRegister(moduleName, names, models), 'utf8');\n\n return {\n out,\n files: [...seen].sort((a, b) => a.localeCompare(b)),\n counts: Object.fromEntries(REGISTER_KEYS.map((key) => [key, names[key].size])) as Record<RegisterKey, number>,\n };\n}\n\nasync function scanModel(\n filePath: string,\n root: string,\n seen: Set<string>,\n names: Record<RegisterKey, Set<string>>\n) {\n const normalized = path.normalize(filePath);\n if (seen.has(normalized)) return;\n seen.add(normalized);\n\n const xml = await readFile(normalized, 'utf8');\n collectSimpleTagNames(xml, 'body', names.bodies);\n collectSimpleTagNames(xml, 'joint', names.joints);\n collectSimpleTagNames(xml, 'site', names.sites);\n collectSimpleTagNames(xml, 'geom', names.geoms);\n collectSimpleTagNames(xml, 'key', names.keyframes);\n collectSectionNames(xml, 'actuator', names.actuators);\n collectSectionNames(xml, 'sensor', names.sensors);\n\n for (const includePath of collectIncludePaths(xml)) {\n const next = path.resolve(path.dirname(normalized), includePath);\n if (next.startsWith(root)) await scanModel(next, root, seen, names);\n }\n}\n\nfunction collectSimpleTagNames(xml: string, tag: string, target: Set<string>) {\n const pattern = new RegExp(`<\\\\s*${tag}\\\\b([^>]*)>`, 'gi');\n for (const match of xml.matchAll(pattern)) {\n const name = readAttr(match[1], 'name');\n if (name) target.add(name);\n }\n}\n\nfunction collectSectionNames(xml: string, section: string, target: Set<string>) {\n const sectionPattern = new RegExp(`<\\\\s*${section}\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\s*/\\\\s*${section}\\\\s*>`, 'gi');\n for (const sectionMatch of xml.matchAll(sectionPattern)) {\n const tagPattern = /<\\s*[a-zA-Z0-9_:-]+\\b([^>]*)>/g;\n for (const tagMatch of sectionMatch[1].matchAll(tagPattern)) {\n const name = readAttr(tagMatch[1], 'name');\n if (name) target.add(name);\n }\n }\n}\n\nfunction collectIncludePaths(xml: string): string[] {\n const result: string[] = [];\n const pattern = /<\\s*include\\b([^>]*)>/gi;\n for (const match of xml.matchAll(pattern)) {\n const file = readAttr(match[1], 'file');\n if (file && !file.includes('://') && !file.startsWith('/')) result.push(file);\n }\n return result;\n}\n\nfunction readAttr(attrs: string, attr: string): string | undefined {\n const pattern = new RegExp(`\\\\b${attr}\\\\s*=\\\\s*(['\"])(.*?)\\\\1`, 'i');\n return attrs.match(pattern)?.[2];\n}\n\nfunction renderRegister(\n moduleName: string,\n names: Record<RegisterKey, Set<string>>,\n models: readonly ModelEntry[]\n): string {\n const fields = REGISTER_KEYS\n .filter((key) => names[key].size > 0)\n .map((key) => ` ${key}: ${renderUnion(names[key])};`);\n const robots = models\n .map((model) => ` ${quoteProperty(model.id)}: {\\n${renderRobotFields(model.names)}\\n };`);\n\n return `// Auto-generated by mujoco-react. Do not edit.\n// Regenerate by running Vite with the mujocoReact() plugin or \\`mujoco-react codegen\\`.\n\nimport 'mujoco-react';\n\ndeclare module '${moduleName}' {\n interface Register {\n robots: {\n${robots.join('\\n')}\n };\n${fields.join('\\n')}\n }\n}\n`;\n}\n\nfunction renderRobotFields(names: Record<RegisterKey, Set<string>>): string {\n return REGISTER_KEYS\n .map((key) => ` ${key}: ${names[key].size > 0 ? renderUnion(names[key]) : 'never'};`)\n .join('\\n');\n}\n\nfunction renderUnion(values: Set<string>): string {\n return [...values].sort((a, b) => a.localeCompare(b)).map((value) => `'${escapeTs(value)}'`).join(' | ');\n}\n\nfunction escapeTs(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nfunction quoteProperty(value: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : `'${escapeTs(value)}'`;\n}\n\nfunction normalizeModels(input: ModelInput): ModelEntry[] {\n if (typeof input === 'string') return [createModelEntry(deriveModelId(input), input)];\n if (Array.isArray(input)) return input.map((file) => createModelEntry(deriveModelId(file), file));\n return Object.entries(input).map(([id, file]) => createModelEntry(sanitizeModelId(id), file));\n}\n\nfunction createModelEntry(id: string, file: string): ModelEntry {\n return { id, file, names: createEmptyNames() };\n}\n\nfunction deriveModelId(file: string): string {\n const normalized = file.replace(/\\\\/g, '/');\n const parts = normalized.split('/').filter(Boolean);\n const filename = parts.at(-1) ?? 'model';\n const parent = parts.length > 1 ? parts.at(-2) : undefined;\n const base = filename.replace(/\\.(xml|mjcf|urdf)$/i, '');\n return sanitizeModelId(parent && ['scene', 'model', 'robot'].includes(base.toLowerCase()) ? parent : base);\n}\n\nfunction sanitizeModelId(value: string): string {\n const sanitized = value.replace(/[^A-Za-z0-9_$]/g, '_').replace(/^[^A-Za-z_$]+/, '');\n return sanitized || 'model';\n}\n\nfunction mergeNames(target: Record<RegisterKey, Set<string>>, source: Record<RegisterKey, Set<string>>) {\n for (const key of REGISTER_KEYS) {\n for (const value of source[key]) target[key].add(value);\n }\n}\n\nfunction shouldRegenerate(file: string, watchedFiles: string[], models: readonly ModelEntry[], root: string): boolean {\n const absolute = path.resolve(file);\n if (watchedFiles.includes(absolute)) return true;\n if (!MODEL_EXTENSIONS.has(path.extname(absolute).toLowerCase())) return false;\n const modelDirs = models.map((model) => path.dirname(path.resolve(root, model.file)));\n return modelDirs.some((dir) => absolute.startsWith(dir));\n}\n"]}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -142,6 +142,16 @@ export type {
|
|
|
142
142
|
JointStateResult,
|
|
143
143
|
// Register (type-safe named resources)
|
|
144
144
|
Register,
|
|
145
|
+
RegisteredRobotMap,
|
|
146
|
+
RobotResource,
|
|
147
|
+
Robots,
|
|
148
|
+
RobotActuators,
|
|
149
|
+
RobotSensors,
|
|
150
|
+
RobotBodies,
|
|
151
|
+
RobotJoints,
|
|
152
|
+
RobotSites,
|
|
153
|
+
RobotGeoms,
|
|
154
|
+
RobotKeyframes,
|
|
145
155
|
Actuators,
|
|
146
156
|
Sensors,
|
|
147
157
|
Bodies,
|
package/src/types.ts
CHANGED
|
@@ -17,6 +17,13 @@ import * as THREE from 'three';
|
|
|
17
17
|
* ```ts
|
|
18
18
|
* declare module 'mujoco-react' {
|
|
19
19
|
* interface Register {
|
|
20
|
+
* robots: {
|
|
21
|
+
* panda: {
|
|
22
|
+
* actuators: 'joint1' | 'joint2' | 'gripper';
|
|
23
|
+
* sensors: 'force_sensor' | 'torque_sensor';
|
|
24
|
+
* bodies: 'link0' | 'link1' | 'hand';
|
|
25
|
+
* };
|
|
26
|
+
* };
|
|
20
27
|
* actuators: 'joint1' | 'joint2' | 'gripper';
|
|
21
28
|
* sensors: 'force_sensor' | 'torque_sensor';
|
|
22
29
|
* bodies: 'link0' | 'link1' | 'hand';
|
|
@@ -28,6 +35,26 @@ import * as THREE from 'three';
|
|
|
28
35
|
*/
|
|
29
36
|
export interface Register {}
|
|
30
37
|
|
|
38
|
+
export type RegisteredRobotMap = Register extends { robots: infer T extends Record<string, Record<string, string>> }
|
|
39
|
+
? T
|
|
40
|
+
: never;
|
|
41
|
+
export type Robots = [RegisteredRobotMap] extends [never] ? string : Extract<keyof RegisteredRobotMap, string>;
|
|
42
|
+
export type RobotResource<TRobot extends string, TKey extends string> =
|
|
43
|
+
[RegisteredRobotMap] extends [never]
|
|
44
|
+
? string
|
|
45
|
+
: TRobot extends keyof RegisteredRobotMap
|
|
46
|
+
? TKey extends keyof RegisteredRobotMap[TRobot]
|
|
47
|
+
? RegisteredRobotMap[TRobot][TKey]
|
|
48
|
+
: string
|
|
49
|
+
: never;
|
|
50
|
+
export type RobotActuators<TRobot extends string> = RobotResource<TRobot, 'actuators'>;
|
|
51
|
+
export type RobotSensors<TRobot extends string> = RobotResource<TRobot, 'sensors'>;
|
|
52
|
+
export type RobotBodies<TRobot extends string> = RobotResource<TRobot, 'bodies'>;
|
|
53
|
+
export type RobotJoints<TRobot extends string> = RobotResource<TRobot, 'joints'>;
|
|
54
|
+
export type RobotSites<TRobot extends string> = RobotResource<TRobot, 'sites'>;
|
|
55
|
+
export type RobotGeoms<TRobot extends string> = RobotResource<TRobot, 'geoms'>;
|
|
56
|
+
export type RobotKeyframes<TRobot extends string> = RobotResource<TRobot, 'keyframes'>;
|
|
57
|
+
|
|
31
58
|
export type Actuators = Register extends { actuators: infer T extends string } ? T : string;
|
|
32
59
|
export type Sensors = Register extends { sensors: infer T extends string } ? T : string;
|
|
33
60
|
export type Bodies = Register extends { bodies: infer T extends string } ? T : string;
|
package/src/vite.ts
CHANGED
|
@@ -15,8 +15,8 @@ type ViteServer = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export interface MujocoReactPluginOptions {
|
|
18
|
-
/** Entry MJCF/URDF files to scan. */
|
|
19
|
-
models:
|
|
18
|
+
/** Entry MJCF/URDF files to scan. Prefer a record for stable per-robot type names. */
|
|
19
|
+
models: ModelInput;
|
|
20
20
|
/** Generated declaration file. Defaults to `src/mujoco-register.gen.d.ts`. */
|
|
21
21
|
generatedRegister?: string;
|
|
22
22
|
/** Module name to augment. Defaults to `mujoco-react`. */
|
|
@@ -26,7 +26,7 @@ export interface MujocoReactPluginOptions {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export interface MujocoRegisterCodegenOptions {
|
|
29
|
-
models:
|
|
29
|
+
models: ModelInput;
|
|
30
30
|
out: string;
|
|
31
31
|
moduleName?: string;
|
|
32
32
|
root?: string;
|
|
@@ -39,6 +39,13 @@ export interface MujocoRegisterCodegenResult {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
type RegisterKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes';
|
|
42
|
+
export type ModelInput = string | readonly string[] | Record<string, string>;
|
|
43
|
+
|
|
44
|
+
interface ModelEntry {
|
|
45
|
+
id: string;
|
|
46
|
+
file: string;
|
|
47
|
+
names: Record<RegisterKey, Set<string>>;
|
|
48
|
+
}
|
|
42
49
|
|
|
43
50
|
const REGISTER_KEYS: RegisterKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes'];
|
|
44
51
|
const MODEL_EXTENSIONS = new Set(['.xml', '.mjcf', '.urdf']);
|
|
@@ -56,14 +63,14 @@ function createEmptyNames(): Record<RegisterKey, Set<string>> {
|
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
export function mujocoReact(options: MujocoReactPluginOptions) {
|
|
59
|
-
const models =
|
|
66
|
+
const models = normalizeModels(options.models);
|
|
60
67
|
let root = process.cwd();
|
|
61
68
|
let generatedRegister = options.generatedRegister ?? 'src/mujoco-register.gen.d.ts';
|
|
62
69
|
let watchedFiles: string[] = [];
|
|
63
70
|
|
|
64
71
|
async function generate() {
|
|
65
72
|
const result = await generateMujocoRegister({
|
|
66
|
-
models,
|
|
73
|
+
models: options.models,
|
|
67
74
|
out: generatedRegister,
|
|
68
75
|
moduleName: options.moduleName,
|
|
69
76
|
root,
|
|
@@ -112,15 +119,17 @@ export async function generateMujocoRegister(
|
|
|
112
119
|
const root = path.resolve(options.root ?? process.cwd());
|
|
113
120
|
const out = path.resolve(root, options.out);
|
|
114
121
|
const moduleName = options.moduleName ?? 'mujoco-react';
|
|
122
|
+
const models = normalizeModels(options.models);
|
|
115
123
|
const names = createEmptyNames();
|
|
116
124
|
const seen = new Set<string>();
|
|
117
125
|
|
|
118
|
-
for (const model of
|
|
119
|
-
await scanModel(path.resolve(root, model), root, seen, names);
|
|
126
|
+
for (const model of models) {
|
|
127
|
+
await scanModel(path.resolve(root, model.file), root, seen, model.names);
|
|
128
|
+
mergeNames(names, model.names);
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
await mkdir(path.dirname(out), { recursive: true });
|
|
123
|
-
await writeFile(out, renderRegister(moduleName, names), 'utf8');
|
|
132
|
+
await writeFile(out, renderRegister(moduleName, names, models), 'utf8');
|
|
124
133
|
|
|
125
134
|
return {
|
|
126
135
|
out,
|
|
@@ -188,10 +197,16 @@ function readAttr(attrs: string, attr: string): string | undefined {
|
|
|
188
197
|
return attrs.match(pattern)?.[2];
|
|
189
198
|
}
|
|
190
199
|
|
|
191
|
-
function renderRegister(
|
|
200
|
+
function renderRegister(
|
|
201
|
+
moduleName: string,
|
|
202
|
+
names: Record<RegisterKey, Set<string>>,
|
|
203
|
+
models: readonly ModelEntry[]
|
|
204
|
+
): string {
|
|
192
205
|
const fields = REGISTER_KEYS
|
|
193
206
|
.filter((key) => names[key].size > 0)
|
|
194
207
|
.map((key) => ` ${key}: ${renderUnion(names[key])};`);
|
|
208
|
+
const robots = models
|
|
209
|
+
.map((model) => ` ${quoteProperty(model.id)}: {\n${renderRobotFields(model.names)}\n };`);
|
|
195
210
|
|
|
196
211
|
return `// Auto-generated by mujoco-react. Do not edit.
|
|
197
212
|
// Regenerate by running Vite with the mujocoReact() plugin or \`mujoco-react codegen\`.
|
|
@@ -200,12 +215,21 @@ import 'mujoco-react';
|
|
|
200
215
|
|
|
201
216
|
declare module '${moduleName}' {
|
|
202
217
|
interface Register {
|
|
218
|
+
robots: {
|
|
219
|
+
${robots.join('\n')}
|
|
220
|
+
};
|
|
203
221
|
${fields.join('\n')}
|
|
204
222
|
}
|
|
205
223
|
}
|
|
206
224
|
`;
|
|
207
225
|
}
|
|
208
226
|
|
|
227
|
+
function renderRobotFields(names: Record<RegisterKey, Set<string>>): string {
|
|
228
|
+
return REGISTER_KEYS
|
|
229
|
+
.map((key) => ` ${key}: ${names[key].size > 0 ? renderUnion(names[key]) : 'never'};`)
|
|
230
|
+
.join('\n');
|
|
231
|
+
}
|
|
232
|
+
|
|
209
233
|
function renderUnion(values: Set<string>): string {
|
|
210
234
|
return [...values].sort((a, b) => a.localeCompare(b)).map((value) => `'${escapeTs(value)}'`).join(' | ');
|
|
211
235
|
}
|
|
@@ -214,10 +238,44 @@ function escapeTs(value: string): string {
|
|
|
214
238
|
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
215
239
|
}
|
|
216
240
|
|
|
217
|
-
function
|
|
241
|
+
function quoteProperty(value: string): string {
|
|
242
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : `'${escapeTs(value)}'`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function normalizeModels(input: ModelInput): ModelEntry[] {
|
|
246
|
+
if (typeof input === 'string') return [createModelEntry(deriveModelId(input), input)];
|
|
247
|
+
if (Array.isArray(input)) return input.map((file) => createModelEntry(deriveModelId(file), file));
|
|
248
|
+
return Object.entries(input).map(([id, file]) => createModelEntry(sanitizeModelId(id), file));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function createModelEntry(id: string, file: string): ModelEntry {
|
|
252
|
+
return { id, file, names: createEmptyNames() };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function deriveModelId(file: string): string {
|
|
256
|
+
const normalized = file.replace(/\\/g, '/');
|
|
257
|
+
const parts = normalized.split('/').filter(Boolean);
|
|
258
|
+
const filename = parts.at(-1) ?? 'model';
|
|
259
|
+
const parent = parts.length > 1 ? parts.at(-2) : undefined;
|
|
260
|
+
const base = filename.replace(/\.(xml|mjcf|urdf)$/i, '');
|
|
261
|
+
return sanitizeModelId(parent && ['scene', 'model', 'robot'].includes(base.toLowerCase()) ? parent : base);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function sanitizeModelId(value: string): string {
|
|
265
|
+
const sanitized = value.replace(/[^A-Za-z0-9_$]/g, '_').replace(/^[^A-Za-z_$]+/, '');
|
|
266
|
+
return sanitized || 'model';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function mergeNames(target: Record<RegisterKey, Set<string>>, source: Record<RegisterKey, Set<string>>) {
|
|
270
|
+
for (const key of REGISTER_KEYS) {
|
|
271
|
+
for (const value of source[key]) target[key].add(value);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function shouldRegenerate(file: string, watchedFiles: string[], models: readonly ModelEntry[], root: string): boolean {
|
|
218
276
|
const absolute = path.resolve(file);
|
|
219
277
|
if (watchedFiles.includes(absolute)) return true;
|
|
220
278
|
if (!MODEL_EXTENSIONS.has(path.extname(absolute).toLowerCase())) return false;
|
|
221
|
-
const modelDirs = models.map((model) => path.dirname(path.resolve(root, model)));
|
|
279
|
+
const modelDirs = models.map((model) => path.dirname(path.resolve(root, model.file)));
|
|
222
280
|
return modelDirs.some((dir) => absolute.startsWith(dir));
|
|
223
281
|
}
|