feat: release v1.0.1 CADWorld 网站与 LeKiwi 智能抓放
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
web-platform-ci / Standalone decision service (no cloud credentials) (pull_request) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled
lekiwi-compatibility / cpu-compatibility (pull_request) Has been cancelled
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
web-platform-ci / Standalone decision service (no cloud credentials) (pull_request) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled
lekiwi-compatibility / cpu-compatibility (pull_request) Has been cancelled
集成同源 BYOK 会话隔离、精简模型设置、官方订阅入口和 HTTPS 发布运维;保留本地训练/调参与控制能力。同步 npm 版本及 CHANGELOG,记录公网真实 API 验收仍待用户凭据。
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { MainThreadPhysicsAdapter } from '../src/simulation/PhysicsAdapter';
|
||||
import { SimulationSession } from '../src/simulation/SimulationSession';
|
||||
import { ModelBindings } from '../src/simulation/ModelBindings';
|
||||
import { mergeCachedFiles } from '../src/project/cachedFiles';
|
||||
import { compiledProjectPackage } from '../src/project/exportPackage';
|
||||
import { importBrowserFiles } from '../src/project/importer';
|
||||
import { LEKIWI_PROFILE as p, bodyToWheels } from '../src/robot/profiles/lekiwi';
|
||||
import type { ProjectManifest } from '../src/project/types';
|
||||
import { ExternalControlClient } from '../src/robot/ExternalControlClient';
|
||||
@@ -13,6 +16,7 @@ import { surfaceDistance, type Surface } from './meshDistance';
|
||||
const adapter = new MainThreadPhysicsAdapter();
|
||||
let bindings: ModelBindings;
|
||||
let currentManifest: ProjectManifest;
|
||||
let roundtripPackage: ReturnType<typeof compiledProjectPackage> | undefined;
|
||||
let frame: number | undefined;
|
||||
let bridge: ExternalControlClient | undefined;
|
||||
function session() {
|
||||
@@ -169,6 +173,7 @@ const harness = {
|
||||
bridge?.sync();
|
||||
},
|
||||
async boot(assetBase: string) {
|
||||
roundtripPackage = undefined;
|
||||
const response = await fetch(`${assetBase}/source-manifest.json`);
|
||||
if (!response.ok) throw new Error('Run examples/lekiwi/prepare_assets.py first');
|
||||
const source = (await response.json()) as { files: Record<string, string> };
|
||||
@@ -198,6 +203,8 @@ const harness = {
|
||||
map: { kind: 'none' },
|
||||
enhancements: { addActuators: false, addSensors: false, sensorType: 'camera' },
|
||||
});
|
||||
currentManifest = mergeCachedFiles(currentManifest, adapter.cachedSupportFiles());
|
||||
adapter.releaseRetired();
|
||||
bindings = new ModelBindings(session().model);
|
||||
return { version: session().module.mj_versionString(), ...read() };
|
||||
},
|
||||
@@ -778,6 +785,86 @@ const harness = {
|
||||
cancel: await cancel,
|
||||
};
|
||||
},
|
||||
visualStats() {
|
||||
const s = session(),
|
||||
m = s.model,
|
||||
d = s.data;
|
||||
s.module.mj_forward(m, d);
|
||||
const wheels = p.wheels.map((name) => {
|
||||
const wheelBody = bindings.resolve(bindings.bodies, `__lekiwi_${name}`);
|
||||
for (let id = 0; id < m.ngeom; id++) {
|
||||
const geom = m.geom(id);
|
||||
try {
|
||||
if (
|
||||
!geom.name.startsWith('4-Omni-Directional-Wheel_Single_Body') ||
|
||||
Number(m.geom_group[id]) !== 1
|
||||
)
|
||||
continue;
|
||||
let body = Number(m.geom_bodyid[id]);
|
||||
while (body && body !== wheelBody) body = Number(m.body_parentid[body]);
|
||||
if (body !== wheelBody) continue;
|
||||
const meshId = Number(m.geom_dataid[id]);
|
||||
const centerError = Math.hypot(
|
||||
...[0, 1, 2].map((i) => d.geom_xpos[id * 3 + i] - d.xpos[wheelBody * 3 + i]),
|
||||
);
|
||||
return {
|
||||
name,
|
||||
faces: Number(m.mesh_facenum[meshId]),
|
||||
vertices: Number(m.mesh_vertnum[meshId]),
|
||||
scale: Array.from(m.mesh_scale.slice(meshId * 3, meshId * 3 + 3)),
|
||||
centerError,
|
||||
rotation: Array.from(d.geom_xmat.slice(id * 9, id * 9 + 9), Number),
|
||||
collision: Boolean(m.geom_contype[id] || m.geom_conaffinity[id]),
|
||||
mass: Number(m.body_mass[Number(m.geom_bodyid[id])]),
|
||||
};
|
||||
} finally {
|
||||
geom.delete();
|
||||
}
|
||||
}
|
||||
throw new Error(`missing CAD wheel ${name}`);
|
||||
});
|
||||
const fs = s.module as unknown as { FS: { readdir(path: string): string[] } };
|
||||
return {
|
||||
wheels,
|
||||
heap: (d.qpos as Float64Array).buffer.byteLength,
|
||||
projectBytes: currentManifest.files.reduce((sum, file) => sum + file.data.byteLength, 0),
|
||||
xmlFiles: currentManifest.files
|
||||
.filter((f) => f.path.endsWith('.xml'))
|
||||
.map((f) => ({ path: f.path, bytes: f.size })),
|
||||
workspaces: fs.FS.readdir('/workspace').filter((name) => name !== '.' && name !== '..'),
|
||||
generatedMeshes: currentManifest.files
|
||||
.filter((f) => /stl-obj-v1-.*\.obj$/.test(f.path))
|
||||
.map((f) => ({ path: f.path, bytes: f.size })),
|
||||
};
|
||||
},
|
||||
async packageRoundtrip() {
|
||||
// Reload the SAME exported package. Re-exporting into the imported sources
|
||||
// would intentionally append a new XML snapshot each round, not test leaks.
|
||||
const packaged = (roundtripPackage ??= compiledProjectPackage(
|
||||
currentManifest,
|
||||
'URDF/LeKiwi.urdf',
|
||||
adapter.exportMjcf(),
|
||||
adapter.cachedSupportFiles(),
|
||||
));
|
||||
const imported = await importBrowserFiles([
|
||||
new File([packaged.data.slice().buffer], 'complete.zip'),
|
||||
]);
|
||||
const old = session();
|
||||
await adapter.load(imported, packaged.entryPath, {
|
||||
robotProfileId: p.id,
|
||||
urdfMode: 'mjcf',
|
||||
baseMode: 'floating',
|
||||
map: { kind: 'none' },
|
||||
});
|
||||
adapter.releaseRetired();
|
||||
currentManifest = imported;
|
||||
bindings = new ModelBindings(session().model);
|
||||
return {
|
||||
...harness.visualStats(),
|
||||
oldModelDeleted: old.model.isDeleted(),
|
||||
oldDataDeleted: old.data.isDeleted(),
|
||||
};
|
||||
},
|
||||
async reimport() {
|
||||
const data = adapter.exportMjcf();
|
||||
const file = {
|
||||
|
||||
Reference in New Issue
Block a user