feat: release v1.0.2 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) (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
集成服务器托管模型、自然语言移动与有界抓放、内置 LeKiwi URL 导入和双摄像头;同步部署契约与指定域名 iframe 白名单,保留原有物理安全、会话及调用预算防护。 更新 npm 包及锁文件版本、CHANGELOG 与发布文档。提交前 typecheck、120 项定向前端测试和 44 项后端测试通过(3 项可选跳过);真实 v2 云模型抓放仍待单独验收,不包含运行密钥或构建产物。
This commit is contained in:
@@ -1,3 +1,10 @@
|
||||
import {
|
||||
composeLanguageScene,
|
||||
LANGUAGE_SCENE,
|
||||
resolveTarget,
|
||||
} from '../src/mobile/agent/LanguageScene';
|
||||
import { PickPlaceExecutor } from '../src/mobile/agent/PickPlaceExecutor';
|
||||
import { LeKiwiIK } from '../src/mobile/agent/LeKiwiIK';
|
||||
import { MainThreadPhysicsAdapter } from '../src/simulation/PhysicsAdapter';
|
||||
import { importBrowserFiles } from '../src/project/importer';
|
||||
import type { ProjectManifest } from '../src/project/types';
|
||||
@@ -29,6 +36,100 @@ const api = {
|
||||
if (!response.ok) throw new Error(`A 资产 HTTP ${response.status}`);
|
||||
source = await importBrowserFiles([new File([await response.blob()], 'lekiwi-v1.zip')]);
|
||||
},
|
||||
async calibrateLanguage() {
|
||||
if (!source) throw new Error('尚未导入 A 资产');
|
||||
await adapter.load(source, 'URDF/LeKiwi.urdf', {
|
||||
robotProfileId: 'lekiwi-v1',
|
||||
configureRobotRuntime: false,
|
||||
mobileRobot: PICK_PLACE_ROBOT,
|
||||
urdfMode: 'mjcf',
|
||||
baseMode: 'floating',
|
||||
map: { kind: 'none' },
|
||||
sceneComposer: (xml) => composeLanguageScene(xml, true),
|
||||
});
|
||||
adapter.releaseRetired();
|
||||
const session = adapter.session!;
|
||||
const ik = new LeKiwiIK(session);
|
||||
const before = Array.from(session.data.qpos);
|
||||
try {
|
||||
const targets = [
|
||||
LANGUAGE_SCENE.source,
|
||||
...LANGUAGE_SCENE.targets.map((t) => t.position),
|
||||
[0.237, 0.365, 0.128],
|
||||
[0.277, 0.865, 0.128],
|
||||
];
|
||||
const checks = targets.flatMap((p) => {
|
||||
resolveTarget('coordinates', p);
|
||||
const pose = { x: p[0] - 0.257, y: p[1] - 0.015, yaw: 0 };
|
||||
return [p[2], 0.195, 0.205, 0.223].map((z) => ({
|
||||
target: [p[0], p[1], z],
|
||||
residual: ik.solve([p[0], p[1], z], pose).residual,
|
||||
}));
|
||||
});
|
||||
return { checks, unchanged: before.every((v, i) => session.data.qpos[i] === v) };
|
||||
} finally {
|
||||
ik.dispose();
|
||||
}
|
||||
},
|
||||
async languagePhysical(goals: number[][], fault: false | 'stop' | 'empty' = false) {
|
||||
await api.calibrateLanguage();
|
||||
const session = adapter.session!;
|
||||
const results = [];
|
||||
for (const goal of goals) {
|
||||
let skills!: PickPlaceExecutor;
|
||||
let cancelled = false;
|
||||
session.startAgent((write) => {
|
||||
const grip = new ModelBindings(session.model).scalarActuator(
|
||||
PICK_PLACE_ROBOT.gripperActuators[0].name,
|
||||
PICK_PLACE_ROBOT.gripperJoint,
|
||||
'position',
|
||||
).actuatorId;
|
||||
skills = new PickPlaceExecutor(
|
||||
session,
|
||||
(id, value) =>
|
||||
write(id, fault === 'empty' && id === grip ? PICK_PLACE_ROBOT.gripperOpen : value),
|
||||
resolveTarget('coordinates', goal),
|
||||
false,
|
||||
);
|
||||
return {
|
||||
beforeStep: () => {
|
||||
if (cancelled) return false;
|
||||
skills.beforeStep();
|
||||
return skills.status === 'running';
|
||||
},
|
||||
afterStep: (dt) => skills.afterStep(dt),
|
||||
hold: () => skills.hold(),
|
||||
cancel: () => {
|
||||
cancelled = true;
|
||||
},
|
||||
dispose: () => skills.dispose(),
|
||||
};
|
||||
});
|
||||
session.setPaused(false);
|
||||
let frames = 0;
|
||||
while (skills.status === 'running' && !cancelled) {
|
||||
session.singleStep();
|
||||
if (++frames % 20 === 0) session.module.mj_forward(session.model, session.data);
|
||||
if (fault === 'stop' && skills.phase === 'carry' && skills.evaluator.transported > 0.08)
|
||||
session.stopAgent('测试运行中停止');
|
||||
if (frames % 200 === 0) await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
results.push({
|
||||
status: skills.status,
|
||||
error: skills.error,
|
||||
goal,
|
||||
sample: skills.sample,
|
||||
transported: skills.evaluator.transported,
|
||||
succeeded: skills.evaluator.succeeded,
|
||||
maxJointSpeed: skills.maxJointSpeed,
|
||||
trace: skills.trace,
|
||||
cancelled,
|
||||
});
|
||||
session.stopAgent('回合结束');
|
||||
if (skills.status !== 'succeeded') break;
|
||||
}
|
||||
return results;
|
||||
},
|
||||
async baseline(seed: number, governed = false) {
|
||||
const epoch = ++generation;
|
||||
task?.dispose();
|
||||
|
||||
Reference in New Issue
Block a user