mujoco-react 8.6.0 → 8.8.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 +103 -45
- 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 +74 -8
- 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 +103 -12
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,31 +127,95 @@ 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
|
+
};`);
|
|
135
|
+
const namespaceAliases = renderNamespaceAliases(models);
|
|
130
136
|
return `// Auto-generated by mujoco-react. Do not edit.
|
|
131
137
|
// Regenerate by running Vite with the mujocoReact() plugin or \`mujoco-react codegen\`.
|
|
132
138
|
|
|
133
|
-
import '
|
|
139
|
+
import '${moduleName}';
|
|
140
|
+
import type { RobotResource } from '${moduleName}';
|
|
134
141
|
|
|
135
142
|
declare module '${moduleName}' {
|
|
136
143
|
interface Register {
|
|
144
|
+
robots: {
|
|
145
|
+
${robots.join("\n")}
|
|
146
|
+
};
|
|
137
147
|
${fields.join("\n")}
|
|
138
148
|
}
|
|
149
|
+
|
|
150
|
+
${namespaceAliases}
|
|
139
151
|
}
|
|
140
152
|
`;
|
|
141
153
|
}
|
|
154
|
+
function renderRobotFields(names) {
|
|
155
|
+
return REGISTER_KEYS.map((key) => ` ${key}: ${names[key].size > 0 ? renderUnion(names[key]) : "never"};`).join("\n");
|
|
156
|
+
}
|
|
142
157
|
function renderUnion(values) {
|
|
143
158
|
return [...values].sort((a, b) => a.localeCompare(b)).map((value) => `'${escapeTs(value)}'`).join(" | ");
|
|
144
159
|
}
|
|
160
|
+
function renderNamespaceAliases(models) {
|
|
161
|
+
const namespaces = {
|
|
162
|
+
actuators: "RobotActuators",
|
|
163
|
+
sensors: "RobotSensors",
|
|
164
|
+
bodies: "RobotBodies",
|
|
165
|
+
joints: "RobotJoints",
|
|
166
|
+
sites: "RobotSites",
|
|
167
|
+
geoms: "RobotGeoms",
|
|
168
|
+
keyframes: "RobotKeyframes"
|
|
169
|
+
};
|
|
170
|
+
const blocks = REGISTER_KEYS.map((key) => {
|
|
171
|
+
const aliases = models.filter((model) => isIdentifier(model.id)).map((model) => ` export type ${model.id} = RobotResource<'${escapeTs(model.id)}', '${key}'>;`);
|
|
172
|
+
if (aliases.length === 0) return "";
|
|
173
|
+
return ` export namespace ${namespaces[key]} {
|
|
174
|
+
${aliases.join("\n")}
|
|
175
|
+
}`;
|
|
176
|
+
}).filter(Boolean).join("\n\n");
|
|
177
|
+
return blocks ? `${blocks}
|
|
178
|
+
` : "";
|
|
179
|
+
}
|
|
145
180
|
function escapeTs(value) {
|
|
146
181
|
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
147
182
|
}
|
|
183
|
+
function quoteProperty(value) {
|
|
184
|
+
return isIdentifier(value) ? value : `'${escapeTs(value)}'`;
|
|
185
|
+
}
|
|
186
|
+
function isIdentifier(value) {
|
|
187
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
|
|
188
|
+
}
|
|
189
|
+
function normalizeModels(input) {
|
|
190
|
+
if (typeof input === "string") return [createModelEntry(deriveModelId(input), input)];
|
|
191
|
+
if (Array.isArray(input)) return input.map((file) => createModelEntry(deriveModelId(file), file));
|
|
192
|
+
return Object.entries(input).map(([id, file]) => createModelEntry(sanitizeModelId(id), file));
|
|
193
|
+
}
|
|
194
|
+
function createModelEntry(id, file) {
|
|
195
|
+
return { id, file, names: createEmptyNames() };
|
|
196
|
+
}
|
|
197
|
+
function deriveModelId(file) {
|
|
198
|
+
const normalized = file.replace(/\\/g, "/");
|
|
199
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
200
|
+
const filename = parts.at(-1) ?? "model";
|
|
201
|
+
const parent = parts.length > 1 ? parts.at(-2) : void 0;
|
|
202
|
+
const base = filename.replace(/\.(xml|mjcf|urdf)$/i, "");
|
|
203
|
+
return sanitizeModelId(parent && ["scene", "model", "robot"].includes(base.toLowerCase()) ? parent : base);
|
|
204
|
+
}
|
|
205
|
+
function sanitizeModelId(value) {
|
|
206
|
+
const sanitized = value.replace(/[^A-Za-z0-9_$]/g, "_").replace(/^[^A-Za-z_$]+/, "");
|
|
207
|
+
return sanitized || "model";
|
|
208
|
+
}
|
|
209
|
+
function mergeNames(target, source) {
|
|
210
|
+
for (const key of REGISTER_KEYS) {
|
|
211
|
+
for (const value of source[key]) target[key].add(value);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
148
214
|
function shouldRegenerate(file, watchedFiles, models, root) {
|
|
149
215
|
const absolute = path.resolve(file);
|
|
150
216
|
if (watchedFiles.includes(absolute)) return true;
|
|
151
217
|
if (!MODEL_EXTENSIONS.has(path.extname(absolute).toLowerCase())) return false;
|
|
152
|
-
const modelDirs = models.map((model) => path.dirname(path.resolve(root, model)));
|
|
218
|
+
const modelDirs = models.map((model) => path.dirname(path.resolve(root, model.file)));
|
|
153
219
|
return modelDirs.some((dir) => absolute.startsWith(dir));
|
|
154
220
|
}
|
|
155
221
|
/**
|
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":"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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. 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 const namespaceAliases = renderNamespaceAliases(models);\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 '${moduleName}';\nimport type { RobotResource } from '${moduleName}';\n\ndeclare module '${moduleName}' {\n interface Register {\n robots: {\n${robots.join('\\n')}\n };\n${fields.join('\\n')}\n }\n\n${namespaceAliases}\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 renderNamespaceAliases(models: readonly ModelEntry[]): string {\n const namespaces: Record<RegisterKey, string> = {\n actuators: 'RobotActuators',\n sensors: 'RobotSensors',\n bodies: 'RobotBodies',\n joints: 'RobotJoints',\n sites: 'RobotSites',\n geoms: 'RobotGeoms',\n keyframes: 'RobotKeyframes',\n };\n\n const blocks = REGISTER_KEYS\n .map((key) => {\n const aliases = models\n .filter((model) => isIdentifier(model.id))\n .map((model) => ` export type ${model.id} = RobotResource<'${escapeTs(model.id)}', '${key}'>;`);\n if (aliases.length === 0) return '';\n return ` export namespace ${namespaces[key]} {\\n${aliases.join('\\n')}\\n }`;\n })\n .filter(Boolean)\n .join('\\n\\n');\n\n return blocks ? `${blocks}\\n` : '';\n}\n\nfunction escapeTs(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nfunction quoteProperty(value: string): string {\n return isIdentifier(value) ? value : `'${escapeTs(value)}'`;\n}\n\nfunction isIdentifier(value: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(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,36 +197,118 @@ 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 };`);
|
|
210
|
+
const namespaceAliases = renderNamespaceAliases(models);
|
|
195
211
|
|
|
196
212
|
return `// Auto-generated by mujoco-react. Do not edit.
|
|
197
213
|
// Regenerate by running Vite with the mujocoReact() plugin or \`mujoco-react codegen\`.
|
|
198
214
|
|
|
199
|
-
import '
|
|
215
|
+
import '${moduleName}';
|
|
216
|
+
import type { RobotResource } from '${moduleName}';
|
|
200
217
|
|
|
201
218
|
declare module '${moduleName}' {
|
|
202
219
|
interface Register {
|
|
220
|
+
robots: {
|
|
221
|
+
${robots.join('\n')}
|
|
222
|
+
};
|
|
203
223
|
${fields.join('\n')}
|
|
204
224
|
}
|
|
225
|
+
|
|
226
|
+
${namespaceAliases}
|
|
205
227
|
}
|
|
206
228
|
`;
|
|
207
229
|
}
|
|
208
230
|
|
|
231
|
+
function renderRobotFields(names: Record<RegisterKey, Set<string>>): string {
|
|
232
|
+
return REGISTER_KEYS
|
|
233
|
+
.map((key) => ` ${key}: ${names[key].size > 0 ? renderUnion(names[key]) : 'never'};`)
|
|
234
|
+
.join('\n');
|
|
235
|
+
}
|
|
236
|
+
|
|
209
237
|
function renderUnion(values: Set<string>): string {
|
|
210
238
|
return [...values].sort((a, b) => a.localeCompare(b)).map((value) => `'${escapeTs(value)}'`).join(' | ');
|
|
211
239
|
}
|
|
212
240
|
|
|
241
|
+
function renderNamespaceAliases(models: readonly ModelEntry[]): string {
|
|
242
|
+
const namespaces: Record<RegisterKey, string> = {
|
|
243
|
+
actuators: 'RobotActuators',
|
|
244
|
+
sensors: 'RobotSensors',
|
|
245
|
+
bodies: 'RobotBodies',
|
|
246
|
+
joints: 'RobotJoints',
|
|
247
|
+
sites: 'RobotSites',
|
|
248
|
+
geoms: 'RobotGeoms',
|
|
249
|
+
keyframes: 'RobotKeyframes',
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const blocks = REGISTER_KEYS
|
|
253
|
+
.map((key) => {
|
|
254
|
+
const aliases = models
|
|
255
|
+
.filter((model) => isIdentifier(model.id))
|
|
256
|
+
.map((model) => ` export type ${model.id} = RobotResource<'${escapeTs(model.id)}', '${key}'>;`);
|
|
257
|
+
if (aliases.length === 0) return '';
|
|
258
|
+
return ` export namespace ${namespaces[key]} {\n${aliases.join('\n')}\n }`;
|
|
259
|
+
})
|
|
260
|
+
.filter(Boolean)
|
|
261
|
+
.join('\n\n');
|
|
262
|
+
|
|
263
|
+
return blocks ? `${blocks}\n` : '';
|
|
264
|
+
}
|
|
265
|
+
|
|
213
266
|
function escapeTs(value: string): string {
|
|
214
267
|
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
215
268
|
}
|
|
216
269
|
|
|
217
|
-
function
|
|
270
|
+
function quoteProperty(value: string): string {
|
|
271
|
+
return isIdentifier(value) ? value : `'${escapeTs(value)}'`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isIdentifier(value: string): boolean {
|
|
275
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function normalizeModels(input: ModelInput): ModelEntry[] {
|
|
279
|
+
if (typeof input === 'string') return [createModelEntry(deriveModelId(input), input)];
|
|
280
|
+
if (Array.isArray(input)) return input.map((file) => createModelEntry(deriveModelId(file), file));
|
|
281
|
+
return Object.entries(input).map(([id, file]) => createModelEntry(sanitizeModelId(id), file));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function createModelEntry(id: string, file: string): ModelEntry {
|
|
285
|
+
return { id, file, names: createEmptyNames() };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function deriveModelId(file: string): string {
|
|
289
|
+
const normalized = file.replace(/\\/g, '/');
|
|
290
|
+
const parts = normalized.split('/').filter(Boolean);
|
|
291
|
+
const filename = parts.at(-1) ?? 'model';
|
|
292
|
+
const parent = parts.length > 1 ? parts.at(-2) : undefined;
|
|
293
|
+
const base = filename.replace(/\.(xml|mjcf|urdf)$/i, '');
|
|
294
|
+
return sanitizeModelId(parent && ['scene', 'model', 'robot'].includes(base.toLowerCase()) ? parent : base);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function sanitizeModelId(value: string): string {
|
|
298
|
+
const sanitized = value.replace(/[^A-Za-z0-9_$]/g, '_').replace(/^[^A-Za-z_$]+/, '');
|
|
299
|
+
return sanitized || 'model';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function mergeNames(target: Record<RegisterKey, Set<string>>, source: Record<RegisterKey, Set<string>>) {
|
|
303
|
+
for (const key of REGISTER_KEYS) {
|
|
304
|
+
for (const value of source[key]) target[key].add(value);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function shouldRegenerate(file: string, watchedFiles: string[], models: readonly ModelEntry[], root: string): boolean {
|
|
218
309
|
const absolute = path.resolve(file);
|
|
219
310
|
if (watchedFiles.includes(absolute)) return true;
|
|
220
311
|
if (!MODEL_EXTENSIONS.has(path.extname(absolute).toLowerCase())) return false;
|
|
221
|
-
const modelDirs = models.map((model) => path.dirname(path.resolve(root, model)));
|
|
312
|
+
const modelDirs = models.map((model) => path.dirname(path.resolve(root, model.file)));
|
|
222
313
|
return modelDirs.some((dir) => absolute.startsWith(dir));
|
|
223
314
|
}
|