Files
Web_FreeCAD_Bitbybit/src/chromeRobotHarness.ts

8 lines
5.7 KiB
TypeScript

import { createMockFacade } from './facade/mockFacade'
import { createRobot } from './facade/robot'
type RobotReport = { schemaVersion: 1; status: 'pass' | 'failed'; browserId: 'chrome'; crossOriginIsolated: boolean; geometry?: { sourceShape: string; valid: boolean; volume: number }; trajectory?: { joints: number; waypoints: number; poses: number; status: string; violations: number; maxReach: number; homeX: number; collisions: number; controllerBytes: number; csvBytes: number }; persistence?: { mode: string; byteLength: number; hash: string; roundTrip: boolean; released: boolean }; opfs?: { markerSuite: string; poses: number; markerRemoved: boolean }; afterRelease?: { shapeCount: number; kernelReferenceCount: number }; error?: string }
const sha256 = async (bytes: Uint8Array) => [...new Uint8Array(await crypto.subtle.digest('SHA-256', Uint8Array.from(bytes).buffer))].map((value) => value.toString(16).padStart(2, '0')).join('')
const run = async (): Promise<RobotReport> => { const facade = createMockFacade(); const report: RobotReport = { schemaVersion: 1, status: 'failed', browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true }; try { if (!report.crossOriginIsolated || !navigator.storage?.getDirectory) throw new Error('Chrome Robot evidence requires isolated OPFS.'); await facade.project.list(); const shape = await facade.geometry.createBox({ documentId: 'robot', documentVersion: 1, width: 2, length: 2, height: 2 }); const quality = await facade.geometry.qualityReport(shape); const mass = await facade.geometry.massProperties(shape); const robot = createRobot('arm', 'Robot arm'); robot.addJoint({ id: 'J1', label: 'Base', minimum: -180, maximum: 180, linkLength: 3 }); robot.addJoint({ id: 'J2', label: 'Arm', minimum: -90, maximum: 90, linkLength: 2 }); robot.addJoint({ id: 'J3', label: 'Wrist', minimum: -90, maximum: 90, linkLength: 1 }); robot.addWaypoint({ id: 'home', values: [0, 0, 0] }); robot.addWaypoint({ id: 'pick', values: [90, 45, -30] }); const solved = robot.generateLinearTrajectory(4); const validation = robot.validateTrajectory(); const workspace = robot.workspace(); const home = robot.forwardKinematics([0, 0, 0]); const collisions = robot.collisions([{ id: 'home-obstacle', min: [5.9, -0.1, -0.1], max: [6.1, 0.1, 0.1] }]); const controller = new TextEncoder().encode(robot.exportController()); const csv = new TextEncoder().encode(robot.exportCsv()); const snapshot = robot.snapshot(); const payload = new TextEncoder().encode(JSON.stringify(snapshot)); const stored = await facade.project.resource.put(payload, 'application/vnd.bitbybit.robot+json'); const loaded = await facade.project.resource.get(stored.hash); const roundTrip = loaded !== null && await sha256(loaded) === await sha256(payload); await facade.project.resource.release(stored.hash); const released = await facade.project.resource.get(stored.hash) === null; const directory = await navigator.storage.getDirectory(); const markerName = 'robot-chrome-marker.json'; const marker = await directory.getFileHandle(markerName, { create: true }); const writable = await marker.createWritable(); await writable.write(JSON.stringify({ suite: 'ROBOT-ALL', poses: snapshot.trajectory.length })); await writable.close(); const markerPayload = JSON.parse(await (await marker.getFile()).text()) as { suite: string; poses: number }; await directory.removeEntry(markerName); let markerRemoved = false; try { await directory.getFileHandle(markerName) } catch (error) { markerRemoved = error instanceof DOMException && error.name === 'NotFoundError' }; report.geometry = { sourceShape: shape.id, valid: quality.structuralValid, volume: mass.volume }; report.trajectory = { joints: snapshot.joints.length, waypoints: snapshot.waypoints.length, poses: solved.trajectory.length, status: solved.status, violations: validation.violations.length, maxReach: workspace.maxReach, homeX: home.position[0], collisions: collisions.length, controllerBytes: controller.byteLength, csvBytes: csv.byteLength }; report.persistence = { mode: facade.project.capabilities().mode, byteLength: stored.byteLength, hash: stored.hash, roundTrip, released }; report.opfs = { markerSuite: markerPayload.suite, poses: markerPayload.poses, markerRemoved }; report.status = report.geometry.valid && report.trajectory.joints === 3 && report.trajectory.waypoints === 2 && report.trajectory.poses === 5 && report.trajectory.status === 'generated' && report.trajectory.violations === 0 && report.trajectory.maxReach === 6 && report.trajectory.homeX === 6 && report.trajectory.collisions >= 1 && report.trajectory.controllerBytes > 0 && report.trajectory.csvBytes > 0 && report.persistence.mode === 'sqlite-opfs' && report.persistence.roundTrip && report.persistence.released && report.opfs.markerSuite === 'ROBOT-ALL' && report.opfs.poses === 5 && report.opfs.markerRemoved ? 'pass' : 'failed' } catch (error) { report.error = error instanceof Error ? error.stack || error.message : String(error) } finally { facade.geometry.dispose(); await new Promise((resolve) => setTimeout(resolve, 0)); const capabilities = facade.geometry.capabilities(); report.afterRelease = { shapeCount: capabilities.shapeCount, kernelReferenceCount: capabilities.kernelReferenceCount }; if (capabilities.shapeCount !== 0 || capabilities.kernelReferenceCount !== 0) report.status = 'failed' } return report }
run().then((report) => { ;(window as Window & { __bitbybitRobotReport?: RobotReport }).__bitbybitRobotReport = report; document.documentElement.dataset.status = report.status; document.querySelector('#result')!.textContent = JSON.stringify(report, null, 2) }).catch((error) => { document.documentElement.dataset.status = 'failed'; document.querySelector('#result')!.textContent = String(error) })
export {}