75 lines
9.1 KiB
HTML
75 lines
9.1 KiB
HTML
<!doctype html>
|
|
<meta charset="utf-8">
|
|
<title>Chrome native Groove history harness</title>
|
|
<pre id="result">running</pre>
|
|
<script type="module">
|
|
import { BitbybitGeometryRuntime } from '/src/facade/geometryRuntime.ts'
|
|
import { NativeOcctHistoryWorkerProvider } from '/src/facade/nativeHistoryWorkerClient.ts'
|
|
|
|
const output = document.querySelector('#result')
|
|
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
|
|
let runtime
|
|
let provider
|
|
try {
|
|
const [baseStep, profileStep] = await Promise.all([
|
|
fetch('/native-groove-base.step').then((response) => response.ok ? response.text() : Promise.reject(new Error(`Groove base fixture failed: ${response.status}`))),
|
|
fetch('/native-groove-profile.step').then((response) => response.ok ? response.text() : Promise.reject(new Error(`Groove profile fixture failed: ${response.status}`))),
|
|
])
|
|
runtime = new BitbybitGeometryRuntime()
|
|
const capabilities = await runtime.initialize()
|
|
provider = new NativeOcctHistoryWorkerProvider({ moduleUrl: '/native/occt-history/bitbybit-occt-history.js' })
|
|
const nativeCapabilities = await provider.initialize()
|
|
runtime.configureNativeHistory(provider)
|
|
const context = { documentId: 'chrome-native-groove', documentVersion: 1 }
|
|
const base = await runtime.createBox({ ...context, width: 10, length: 8, height: 10, center: [0, 0, 0], originOnCenter: false })
|
|
const profile = await runtime.importShape({ ...context, format: 'step', text: profileStep })
|
|
const planarProfile = { outer: [[0.5, 2, 0], [1.5, 2, 0], [1.5, 5, 0], [0.5, 5, 0]] }
|
|
const result = await runtime.groove({ ...context, base, profile: planarProfile, axisOrigin: [0, 0, 0], axisDirection: [0, 1, 0], angle: 360 })
|
|
const [quality, mass] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result)])
|
|
const baseQuality = await runtime.qualityReport(base)
|
|
const markerDirectory = await navigator.storage.getDirectory()
|
|
const markerName = 'native-groove-history-chrome-marker.json'
|
|
const marker = await markerDirectory.getFileHandle(markerName, { create: true })
|
|
const writable = await marker.createWritable()
|
|
await writable.write(JSON.stringify({ operation: 'groove', provider: nativeCapabilities.providerId }))
|
|
await writable.close()
|
|
const markerPayload = JSON.parse(await (await marker.getFile()).text())
|
|
const records = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-groove-history', operation: 'groove', inputs: [{ objectId: 'Base', shape: base }, { objectId: 'Profile', shape: profile }], axisOrigin: [0, 0, 0], direction: [0, 1, 0], angle: 360, result })
|
|
const stageCaptures = records.stageCaptures || []
|
|
const twoAngleResult = await runtime.groove({ ...context, base, profile: planarProfile, axisOrigin: [0, 0, 0], axisDirection: [0, 1, 0], angle: 180, profileRotationAngle: -60 })
|
|
const [twoAngleQuality, twoAngleMass] = await Promise.all([runtime.qualityReport(twoAngleResult), runtime.massProperties(twoAngleResult)])
|
|
const twoAngleUnexplainedErrors = (twoAngleQuality.structuralIssues || []).filter((issue) => issue.severity === 'error' && issue.description !== 'CoEdgeDef has no Curve2D representation').length
|
|
const twoAngleRecords = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-groove-two-angles-history', operation: 'groove', inputs: [{ objectId: 'Base', shape: base }, { objectId: 'Profile', shape: profile }], stages: [{ stageId: 'Groove:stage:0', operation: 'groove', inputObjectIds: ['Base', 'Profile'], resultObjectId: 'GrooveTwoAngles', ordinal: 0 }], featureSides: [{ direction: [0, 1, 0], angle: 120 }, { direction: [0, -1, 0], angle: 60 }], axisOrigin: [0, 0, 0], direction: [0, 1, 0], angle: 120, result: twoAngleResult })
|
|
const twoAngleStages = twoAngleRecords.stageCaptures || []
|
|
const modified = records.filter((record) => record.relation === 'modified').length
|
|
const generated = records.filter((record) => record.relation === 'generated').length
|
|
const deleted = records.filter((record) => record.relation === 'deleted').length
|
|
report.capabilities = capabilities
|
|
report.nativeCapabilities = nativeCapabilities
|
|
report.history = { recordCount: records.length, sourceObjects: [...new Set(stageCaptures.flatMap((stage) => stage.records.map((record) => record.sourceObjectId)))].sort(), relations: { modified, generated, deleted }, stages: stageCaptures.map((stage) => ({ stageId: stage.stageId, operation: stage.operation, inputObjectIds: stage.inputObjectIds, resultObjectId: stage.resultObjectId, ordinal: stage.ordinal, topologyEntries: stage.topology.entries.length, recordCount: stage.records.length })) }
|
|
report.twoAngleHistory = { recordCount: twoAngleRecords.length, sourceObjects: [...new Set(twoAngleStages.flatMap((stage) => stage.records.map((record) => record.sourceObjectId)))].sort(), structuralValid: twoAngleQuality.nativeKernelValid === true, graphStructuralValid: twoAngleQuality.structuralValid, graphStructuralErrors: twoAngleQuality.structuralErrors, graphStructuralWarnings: twoAngleQuality.structuralWarnings, graphStructuralIssues: twoAngleQuality.structuralIssues, unexplainedStructuralErrors: twoAngleUnexplainedErrors, solids: twoAngleQuality.solids, volume: twoAngleMass.volume, stages: twoAngleStages.map((stage) => ({ stageId: stage.stageId, operation: stage.operation, inputObjectIds: stage.inputObjectIds, resultObjectId: stage.resultObjectId, ordinal: stage.ordinal, topologyEntries: stage.topology.entries.length, recordCount: stage.records.length })) }
|
|
report.base = { boundingBox: baseQuality.boundingBox, volume: (await runtime.massProperties(base)).volume }
|
|
report.result = { structuralValid: quality.structuralValid, structuralErrors: quality.structuralErrors, structuralWarnings: quality.structuralWarnings, solids: quality.solids, faces: quality.faces, edges: quality.edges, vertices: quality.vertices, volume: mass.volume }
|
|
await runtime.release(twoAngleResult)
|
|
await runtime.release(result)
|
|
await runtime.release(profile)
|
|
await runtime.release(base)
|
|
report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
|
|
await markerDirectory.removeEntry(markerName)
|
|
let markerRemoved = false
|
|
try { await markerDirectory.getFileHandle(markerName) } catch (error) { markerRemoved = error?.name === 'NotFoundError' }
|
|
report.opfs = { markerOperation: markerPayload.operation, markerRemoved }
|
|
const expectedVolume = 800 - 6 * Math.PI
|
|
report.status = report.crossOriginIsolated && capabilities.status === 'ready' && nativeCapabilities.availability === 'available' && nativeCapabilities.operations.includes('groove') && nativeCapabilities.operations.includes('rotate') && records.length > 0 && modified > 0 && generated + deleted > 0 && stageCaptures.length === 2 && stageCaptures[0].operation === 'revolution' && stageCaptures[0].inputObjectIds.join(',') === 'Profile' && stageCaptures[0].records.length > 0 && stageCaptures[0].topology.entries.length > 0 && stageCaptures[1].operation === 'cut' && stageCaptures[1].inputObjectIds.join(',') === `Base,${stageCaptures[0].resultObjectId}` && stageCaptures[1].records.length === records.length && stageCaptures[1].topology.entries.length > 0 && report.history.sourceObjects.includes('Base') && report.history.sourceObjects.includes('Profile') && report.history.sourceObjects.includes(stageCaptures[0].resultObjectId) && quality.structuralValid && quality.solids === 1 && Math.abs(mass.volume - expectedVolume) <= 1e-5 && twoAngleRecords.length > 0 && twoAngleQuality.nativeKernelValid === true && twoAngleUnexplainedErrors === 0 && twoAngleQuality.solids === 1 && Math.abs(twoAngleMass.volume - (800 - 3 * Math.PI)) <= 1e-5 && twoAngleStages.length === 3 && twoAngleStages[0].operation === 'rotate' && twoAngleStages[0].inputObjectIds.join(',') === 'Profile' && twoAngleStages[0].topology.entries.length > 0 && twoAngleStages[1].operation === 'revolution' && twoAngleStages[1].inputObjectIds.join(',') === `${twoAngleStages[0].resultObjectId}` && twoAngleStages[1].topology.entries.length > 0 && twoAngleStages[2].operation === 'cut' && twoAngleStages[2].inputObjectIds.join(',') === `Base,${twoAngleStages[1].resultObjectId}` && twoAngleStages[2].resultObjectId === 'GrooveTwoAngles' && twoAngleStages[2].topology.entries.length > 0 && report.twoAngleHistory.sourceObjects.includes('Base') && report.twoAngleHistory.sourceObjects.includes('Profile') && report.twoAngleHistory.sourceObjects.includes(twoAngleStages[0].resultObjectId) && report.twoAngleHistory.sourceObjects.includes(twoAngleStages[1].resultObjectId) && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 && report.opfs.markerOperation === 'groove' && report.opfs.markerRemoved ? 'pass' : 'failed'
|
|
} catch (error) {
|
|
report.status = 'failed'
|
|
report.error = error instanceof Error ? error.message : String(error)
|
|
} finally {
|
|
provider?.dispose()
|
|
runtime?.dispose()
|
|
}
|
|
output.textContent = JSON.stringify(report, null, 2)
|
|
await fetch('/__native-groove-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
|
|
if (report.status !== 'pass') throw new Error(report.error || 'Chrome native Groove history harness failed.')
|
|
</script>
|