mujoco-react 8.1.0 → 8.1.1
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/dist/index.js +66 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/SceneLoader.ts +77 -51
package/package.json
CHANGED
package/src/core/SceneLoader.ts
CHANGED
|
@@ -137,6 +137,17 @@ function sceneObjectToXml(obj: SceneObject): string {
|
|
|
137
137
|
return `<body name="${obj.name}" pos="${pos}">${joint}<geom type="${obj.type}" size="${size}" rgba="${rgba}" contype="1" conaffinity="1"${mass}${friction}${solref}${solimp}${condim}/></body>`;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
/** Create virtual directory structure for a file path. */
|
|
141
|
+
function ensureDir(mujoco: MujocoModule, fname: string) {
|
|
142
|
+
const dirParts = fname.split('/');
|
|
143
|
+
dirParts.pop();
|
|
144
|
+
let currentPath = '/working';
|
|
145
|
+
for (const part of dirParts) {
|
|
146
|
+
currentPath += '/' + part;
|
|
147
|
+
try { mujoco.FS.mkdir(currentPath); } catch { /* ignore */ }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
140
151
|
interface LoadResult {
|
|
141
152
|
mjModel: MujocoModel;
|
|
142
153
|
mjData: MujocoData;
|
|
@@ -157,15 +168,22 @@ export async function loadScene(
|
|
|
157
168
|
const baseUrl = config.src.endsWith('/') ? config.src : config.src + '/';
|
|
158
169
|
|
|
159
170
|
const downloaded = new Set<string>();
|
|
160
|
-
const
|
|
171
|
+
const xmlQueue: string[] = [config.sceneFile];
|
|
172
|
+
const assetFiles: string[] = [];
|
|
161
173
|
const parser = new DOMParser();
|
|
162
174
|
|
|
163
|
-
//
|
|
164
|
-
while (
|
|
165
|
-
const fname =
|
|
175
|
+
// 2a. Download XML files sequentially (to discover dependencies)
|
|
176
|
+
while (xmlQueue.length > 0) {
|
|
177
|
+
const fname = xmlQueue.shift()!;
|
|
166
178
|
if (downloaded.has(fname)) continue;
|
|
167
179
|
downloaded.add(fname);
|
|
168
180
|
|
|
181
|
+
if (!fname.endsWith('.xml')) {
|
|
182
|
+
// Non-XML discovered during XML scan — collect for parallel download
|
|
183
|
+
assetFiles.push(fname);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
169
187
|
onProgress?.(`Downloading ${fname}...`);
|
|
170
188
|
|
|
171
189
|
const res = await fetch(baseUrl + fname);
|
|
@@ -174,61 +192,69 @@ export async function loadScene(
|
|
|
174
192
|
continue;
|
|
175
193
|
}
|
|
176
194
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// 3. Apply XML patches from config
|
|
190
|
-
for (const patch of config.xmlPatches ?? []) {
|
|
191
|
-
if (fname.endsWith(patch.target) || fname === patch.target) {
|
|
192
|
-
if (patch.replace) {
|
|
193
|
-
const [from, to] = patch.replace;
|
|
194
|
-
if (text.includes(from)) {
|
|
195
|
-
text = text.replace(from, to);
|
|
196
|
-
} else {
|
|
197
|
-
const preview = from.length > 80 ? `${from.slice(0, 80)}...` : from;
|
|
198
|
-
console.warn(`XML patch replace pattern not found in ${fname}: "${preview}"`);
|
|
199
|
-
}
|
|
195
|
+
let text = await res.text();
|
|
196
|
+
|
|
197
|
+
// 3. Apply XML patches from config
|
|
198
|
+
for (const patch of config.xmlPatches ?? []) {
|
|
199
|
+
if (fname.endsWith(patch.target) || fname === patch.target) {
|
|
200
|
+
if (patch.replace) {
|
|
201
|
+
const [from, to] = patch.replace;
|
|
202
|
+
if (text.includes(from)) {
|
|
203
|
+
text = text.replace(from, to);
|
|
204
|
+
} else {
|
|
205
|
+
const preview = from.length > 80 ? `${from.slice(0, 80)}...` : from;
|
|
206
|
+
console.warn(`XML patch replace pattern not found in ${fname}: "${preview}"`);
|
|
200
207
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} else {
|
|
209
|
-
console.warn(`XML patch inject failed in ${fname}: could not find tag end after "${patch.injectAfter}"`);
|
|
210
|
-
}
|
|
208
|
+
}
|
|
209
|
+
if (patch.inject && patch.injectAfter) {
|
|
210
|
+
const idx = text.indexOf(patch.injectAfter);
|
|
211
|
+
if (idx !== -1) {
|
|
212
|
+
const tagEnd = text.indexOf('>', idx + patch.injectAfter.length);
|
|
213
|
+
if (tagEnd !== -1) {
|
|
214
|
+
text = text.slice(0, tagEnd + 1) + patch.inject + text.slice(tagEnd + 1);
|
|
211
215
|
} else {
|
|
212
|
-
|
|
213
|
-
? `${patch.injectAfter.slice(0, 80)}...`
|
|
214
|
-
: patch.injectAfter;
|
|
215
|
-
console.warn(`XML patch inject anchor not found in ${fname}: "${preview}"`);
|
|
216
|
+
console.warn(`XML patch inject failed in ${fname}: could not find tag end after "${patch.injectAfter}"`);
|
|
216
217
|
}
|
|
218
|
+
} else {
|
|
219
|
+
const preview = patch.injectAfter.length > 80
|
|
220
|
+
? `${patch.injectAfter.slice(0, 80)}...`
|
|
221
|
+
: patch.injectAfter;
|
|
222
|
+
console.warn(`XML patch inject anchor not found in ${fname}: "${preview}"`);
|
|
217
223
|
}
|
|
218
224
|
}
|
|
219
225
|
}
|
|
226
|
+
}
|
|
220
227
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
228
|
+
// 4. Inject scene objects into the scene file
|
|
229
|
+
if (fname === config.sceneFile && config.sceneObjects?.length) {
|
|
230
|
+
const xml = config.sceneObjects.map((obj) => sceneObjectToXml(obj)).join('');
|
|
231
|
+
text = text.replace('</worldbody>', xml + '</worldbody>');
|
|
232
|
+
}
|
|
226
233
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
234
|
+
ensureDir(mujoco, fname);
|
|
235
|
+
mujoco.FS.writeFile(`/working/${fname}`, text);
|
|
236
|
+
scanDependencies(text, fname, parser, downloaded, xmlQueue);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 2b. Download all binary assets (meshes, textures) in parallel
|
|
240
|
+
if (assetFiles.length > 0) {
|
|
241
|
+
onProgress?.(`Downloading ${assetFiles.length} assets...`);
|
|
242
|
+
|
|
243
|
+
const results = await Promise.all(
|
|
244
|
+
assetFiles.map(async (fname) => {
|
|
245
|
+
const res = await fetch(baseUrl + fname);
|
|
246
|
+
if (!res.ok) {
|
|
247
|
+
console.warn(`Failed to fetch ${fname}: ${res.status} ${res.statusText}`);
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
return { fname, buffer: new Uint8Array(await res.arrayBuffer()) };
|
|
251
|
+
})
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
for (const result of results) {
|
|
255
|
+
if (!result) continue;
|
|
256
|
+
ensureDir(mujoco, result.fname);
|
|
257
|
+
mujoco.FS.writeFile(`/working/${result.fname}`, result.buffer);
|
|
232
258
|
}
|
|
233
259
|
}
|
|
234
260
|
|