feat: establish reproducible FreeCAD web compatibility baseline

This commit is contained in:
2026-08-10 16:11:38 -04:00
parent e5a5d74dbc
commit b962a5c3b5
733 changed files with 349081 additions and 647 deletions

6
public/_headers Normal file
View File

@@ -0,0 +1,6 @@
/*
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin
Content-Security-Policy: default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; worker-src 'self' blob:; connect-src 'self'; img-src 'self' data: blob:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chrome Addon Harness</title></head><body><pre id="result">running</pre><script type="module" src="/src/chromeAddonHarness.ts"></script></body></html>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Assembly harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeAssemblyHarness.ts"></script>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome BIM harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeBimHarness.ts"></script>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome CAM harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeCamHarness.ts"></script>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native CAM simulation harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeCamNativeSimulationHarness.ts"></script>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Data modules harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeDataHarness.ts"></script>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Draft harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeDraftHarness.ts"></script>

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chrome Engineering Harness</title></head><body><pre id="result">running</pre><script type="module" src="/src/chromeEngineeringHarness.ts"></script></body></html>

View File

@@ -0,0 +1,158 @@
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Chrome fault injection harness</title></head>
<body>
<pre id="status">loading</pre>
<script type="module">
import { createSketch } from '/src/facade/sketcher.ts'
import { PlanegcsWorkerProvider } from '/src/facade/planegcsWorkerClient.ts'
import { NativeOcctHistoryWorkerProvider } from '/src/facade/nativeHistoryWorkerClient.ts'
import { SketchSolverCoordinator } from '/src/facade/sketchSolverProtocol.ts'
const browserId = new URL(location.href).searchParams.get('browser') || 'chrome'
const report = {
schemaVersion: 1,
browserId,
executionContext: 'dedicated-worker',
crossOriginIsolated,
sharedArrayBuffer: typeof SharedArrayBuffer === 'function',
status: 'failed',
}
const wrappers = []
let activeWrapper = null
const workerFactory = () => {
const real = new Worker(new URL('/src/facade/planegcsWorkerEntry.ts', location.href), { type: 'module', name: 'qa-06-planegcs' })
const listeners = { message: new Set(), error: new Set() }
let terminated = false
const wrapper = {
postMessage: (message) => real.postMessage(message),
terminate: () => { terminated = true; real.terminate() },
get terminated() { return terminated },
addEventListener: (type, listener) => { listeners[type].add(listener) },
removeEventListener: (type, listener) => { listeners[type].delete(listener) },
crash: () => {
terminated = true
real.terminate()
const event = { message: 'QA-06 injected Worker crash', error: new Error('QA-06 injected Worker crash') }
for (const listener of listeners.error) listener(event)
},
}
real.addEventListener('message', (event) => { for (const listener of listeners.message) listener(event) })
real.addEventListener('error', (event) => { for (const listener of listeners.error) listener(event) })
wrappers.push(wrapper)
activeWrapper = wrapper
return wrapper
}
const nativeWrappers = []
let activeNativeWrapper = null
const nativeWorkerFactory = () => {
const real = new Worker(new URL('/src/facade/nativeHistoryWorkerEntry.ts', location.href), { type: 'module', name: 'qa-06-native-history' })
const listeners = { message: new Set(), error: new Set() }
let terminated = false
const wrapper = {
postMessage: (message) => real.postMessage(message),
terminate: () => { terminated = true; real.terminate() },
get terminated() { return terminated },
addEventListener: (type, listener) => { listeners[type].add(listener) },
removeEventListener: (type, listener) => { listeners[type].delete(listener) },
crash: () => {
terminated = true
real.terminate()
const event = { message: 'QA-06 injected native history Worker crash', error: new Error('QA-06 injected native history Worker crash') }
for (const listener of listeners.error) listener(event)
},
}
real.addEventListener('message', (event) => { for (const listener of listeners.message) listener(event) })
real.addEventListener('error', (event) => { for (const listener of listeners.error) listener(event) })
nativeWrappers.push(wrapper)
activeNativeWrapper = wrapper
return wrapper
}
const snapshot = createSketch('qa-06-sketch', [{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 3, y: 4 } }], [
{ id: 'horizontal', type: 'horizontal', geometryId: 'line' },
{ id: 'length', type: 'distance', first: { geometryId: 'line', point: 'start' }, second: { geometryId: 'line', point: 'end' }, value: 5 },
])
const request = (requestId) => ({ requestId, documentId: 'qa-06-doc', documentVersion: 1, snapshot })
const settle = (promise) => promise.then(() => 'resolved', (error) => ({ name: error?.name || 'Error', message: String(error?.message || error) }))
let provider
let nativeProvider
let markerName
try {
if (!crossOriginIsolated || typeof SharedArrayBuffer !== 'function') throw new Error('Chrome fault harness requires cross-origin isolation and SharedArrayBuffer.')
if (!navigator.storage?.getDirectory) throw new Error('Chrome OPFS is unavailable.')
const root = await navigator.storage.getDirectory()
markerName = `qa-06-fault-${crypto.randomUUID()}.marker`
const marker = await root.getFileHandle(markerName, { create: true })
await (await marker.createWritable()).close()
const markerBeforeCleanup = await root.getFileHandle(markerName)
const markerBytes = (await (await markerBeforeCleanup.getFile()).arrayBuffer()).byteLength
provider = new PlanegcsWorkerProvider({ workerFactory, initializationTimeoutMs: 30_000 })
const initial = await provider.initialize()
const completed = await new SketchSolverCoordinator(() => 1).solve(provider, request('qa-06-completed'))
const cancellationCoordinator = new SketchSolverCoordinator(() => 1)
const cancellation = cancellationCoordinator.solve(provider, request('qa-06-cancelled'))
cancellationCoordinator.cancel()
const cancelled = await cancellation
const wrapperBeforeCrash = activeWrapper
wrapperBeforeCrash?.crash()
await new Promise((resolve) => setTimeout(resolve, 0))
const unavailableAfterCrash = provider.capabilities().availability === 'unavailable'
const recovered = await new SketchSolverCoordinator(() => 1).solve(provider, request('qa-06-recovered'))
const stale = await new SketchSolverCoordinator(() => 2).solve(provider, request('qa-06-stale'))
const recoveredLine = recovered.response?.result.snapshot.geometry[0]
nativeProvider = new NativeOcctHistoryWorkerProvider({ workerFactory: nativeWorkerFactory, moduleUrl: '/native/occt-history/bitbybit-occt-history.js', initializationTimeoutMs: 30_000 })
const nativeInitial = await nativeProvider.initialize()
const nativeWrapperBeforeCrash = activeNativeWrapper
nativeWrapperBeforeCrash?.crash()
await new Promise((resolve) => setTimeout(resolve, 0))
const nativeUnavailableAfterCrash = nativeProvider.capabilities().availability === 'unavailable'
const nativeRecovered = await nativeProvider.initialize()
await root.removeEntry(markerName)
let markerRemoved = false
try { await root.getFileHandle(markerName); } catch (error) { markerRemoved = error?.name === 'NotFoundError' }
markerName = undefined
const lifecycle = {
initialAvailable: initial.availability === 'available',
completed: completed.status,
cancelled: cancelled.status,
crashInjected: true,
unavailableAfterCrash,
workerFactoryCount: wrappers.length,
recovered: recovered.status,
stale: stale.status,
recoveredEnd: recoveredLine?.type === 'line' ? recoveredLine.end : null,
nativeInitialAvailable: nativeInitial.availability === 'available',
nativeCrashInjected: true,
nativeUnavailableAfterCrash,
nativeWorkerFactoryCount: nativeWrappers.length,
nativeRecoveredAvailable: nativeRecovered.availability === 'available',
}
const opfs = { markerBytes, markerRemoved }
const passed = lifecycle.initialAvailable && lifecycle.completed === 'completed' && lifecycle.cancelled === 'cancelled' && lifecycle.crashInjected && lifecycle.unavailableAfterCrash && lifecycle.workerFactoryCount >= 3 && lifecycle.recovered === 'completed' && lifecycle.stale === 'stale' && Math.abs(lifecycle.recoveredEnd?.x - 5) <= 1e-7 && Math.abs(lifecycle.recoveredEnd?.y) <= 1e-7 && lifecycle.nativeInitialAvailable && lifecycle.nativeCrashInjected && lifecycle.nativeUnavailableAfterCrash && lifecycle.nativeWorkerFactoryCount >= 2 && lifecycle.nativeRecoveredAvailable && opfs.markerBytes === 0 && opfs.markerRemoved
Object.assign(report, { status: passed ? 'pass' : 'failed', lifecycle, opfs, activeWorkersBeforeDispose: wrappers.length, activeNativeWorkersBeforeDispose: nativeWrappers.length })
} catch (error) {
report.error = error instanceof Error ? `${error.name}: ${error.message}` : String(error)
if (markerName) {
try { await (await navigator.storage.getDirectory()).removeEntry(markerName) } catch {}
}
} finally {
provider?.dispose()
nativeProvider?.dispose()
Object.assign(report, { activeWorkersAfterDispose: wrappers.filter((wrapper) => wrapper.terminated).length, activeNativeWorkersAfterDispose: nativeWrappers.filter((wrapper) => wrapper.terminated).length })
}
document.querySelector('#status').textContent = JSON.stringify(report, null, 2)
await fetch('/__chrome-fault-injection-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
</script>
</body>
</html>

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chrome FCStd Round Trip Harness</title></head><body><pre id="result">running</pre><script type="module" src="/src/chromeFcstdRoundTripHarness.ts"></script></body></html>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome FEM harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeFemHarness.ts"></script>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Inspection harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeInspectionHarness.ts"></script>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Mesh harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeMeshHarness.ts"></script>

View File

@@ -0,0 +1,52 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native Chamfer 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', executionContext: 'geometry-and-history-workers', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
let runtime, provider, base, result
try {
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-chamfer', documentVersion: 1 }
base = await runtime.createBox({ ...context, width: 6, length: 6, height: 6, center: [0, 0, 0], originOnCenter: true })
result = await runtime.chamfer({ ...context, base, distance: 0.4, indexes: [0] })
const [quality, mass] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result)])
const markerDirectory = await navigator.storage.getDirectory()
const markerName = 'native-chamfer-history-chrome-marker.json'
const marker = await markerDirectory.getFileHandle(markerName, { create: true })
const writable = await marker.createWritable()
await writable.write(JSON.stringify({ operation: 'chamfer', provider: nativeCapabilities.providerId }))
await writable.close()
const markerText = await (await marker.getFile()).text()
const records = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-chamfer-history', operation: 'chamfer', distance: 0.4, inputs: [{ objectId: 'Base', shape: base }], result })
const relations = { modified: records.filter((record) => record.relation === 'modified').length, generated: records.filter((record) => record.relation === 'generated').length, deleted: records.filter((record) => record.relation === 'deleted').length }
report.capabilities = capabilities
report.nativeCapabilities = nativeCapabilities
report.history = { recordCount: records.length, relations, sourceObjects: [...new Set(records.map((record) => record.sourceObjectId))] }
report.result = { structuralValid: quality.structuralValid, solids: quality.solids, volume: mass.volume }
report.opfs = { markerCreated: true, markerSuite: JSON.parse(markerText).operation, markerRemoved: false }
await markerDirectory.removeEntry(markerName)
report.opfs.markerRemoved = true
await runtime.release(result); result = undefined
await runtime.release(base); base = undefined
report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
report.status = report.crossOriginIsolated && capabilities.status === 'ready' && nativeCapabilities.availability === 'available' && nativeCapabilities.operations.includes('chamfer') && records.length > 0 && relations.modified > 0 && relations.generated > 0 && relations.deleted > 0 && quality.structuralValid && quality.solids === 1 && mass.volume > 0 && mass.volume < 216 && report.opfs.markerSuite === 'chamfer' && report.opfs.markerRemoved && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? 'pass' : 'failed'
} catch (error) {
report.status = 'failed'
report.error = error instanceof Error ? `${error.name}: ${error.message}` : String(error)
} finally {
if (result) await runtime?.release(result).catch(() => {})
if (base) await runtime?.release(base).catch(() => {})
provider?.dispose(); runtime?.dispose()
}
output.textContent = JSON.stringify(report, null, 2)
await fetch('/__native-chamfer-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome native Chamfer history harness failed.')
</script>

View File

@@ -0,0 +1,42 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native Draft 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', executionContext: 'geometry-and-history-workers', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
let runtime, provider, base, result, stage = 'init'
try {
stage = 'initialize'
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-draft', documentVersion: 1 }
stage = 'create-box'; base = await runtime.createBox({ ...context, width: 4, length: 4, height: 4 })
stage = 'bitbybit-draft'; result = await runtime.draft({ ...context, base, angle: 5, direction: [0, 0, 1], neutralPlaneOrigin: [0, 0, 0], neutralPlaneDirection: [0, 0, 1], indexes: [1] })
stage = 'quality'; const [quality, mass] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result)])
const markerDirectory = await navigator.storage.getDirectory()
const markerName = 'native-draft-history-chrome-marker.json'
const marker = await markerDirectory.getFileHandle(markerName, { create: true })
const writable = await marker.createWritable(); await writable.write(JSON.stringify({ operation: 'draft', provider: nativeCapabilities.providerId })); await writable.close()
const markerText = await (await marker.getFile()).text()
stage = 'native-history'; const records = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-draft-history', operation: 'draft', angle: 5, direction: [0, 0, 1], axisOrigin: [0, 0, 0], neutralPlaneDirection: [0, 0, 1], faceIndexes: [1], inputs: [{ objectId: 'Base', shape: base }], result })
const relations = { modified: records.filter((record) => record.relation === 'modified').length, generated: records.filter((record) => record.relation === 'generated').length, deleted: records.filter((record) => record.relation === 'deleted').length }
report.capabilities = capabilities; report.nativeCapabilities = nativeCapabilities
report.history = { recordCount: records.length, relations, sourceObjects: [...new Set(records.map((record) => record.sourceObjectId))] }
report.result = { structuralValid: quality.structuralValid, solids: quality.solids, volume: mass.volume }
report.opfs = { markerCreated: true, markerSuite: JSON.parse(markerText).operation, markerRemoved: false }
await markerDirectory.removeEntry(markerName); report.opfs.markerRemoved = true
await runtime.release(result); result = undefined; await runtime.release(base); base = undefined
report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
report.status = report.crossOriginIsolated && capabilities.status === 'ready' && nativeCapabilities.availability === 'available' && nativeCapabilities.operations.includes('draft') && records.length > 0 && relations.modified > 0 && report.result.structuralValid && report.result.solids === 1 && report.result.volume > 0 && report.opfs.markerSuite === 'draft' && report.opfs.markerRemoved && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? 'pass' : 'failed'
} catch (error) { report.status = 'failed'; report.error = `${stage}: ${error instanceof Error ? `${error.name}: ${error.message}` : String(error)}` }
finally { if (result) await runtime?.release(result).catch(() => {}); if (base) await runtime?.release(base).catch(() => {}); provider?.dispose(); runtime?.dispose() }
output.textContent = JSON.stringify(report, null, 2)
await fetch('/__native-draft-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome native Draft history harness failed.')
</script>

View File

@@ -0,0 +1,59 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native Fillet 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', executionContext: 'geometry-and-history-workers', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
let runtime
let provider
let base
let result
try {
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-fillet', documentVersion: 1 }
base = await runtime.createBox({ ...context, width: 6, length: 6, height: 6, center: [0, 0, 0], originOnCenter: true })
result = await runtime.fillet({ ...context, base, radius: 0.4, indexes: [0] })
const [quality, mass] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result)])
const markerDirectory = await navigator.storage.getDirectory()
const markerName = 'native-fillet-history-chrome-marker.json'
const marker = await markerDirectory.getFileHandle(markerName, { create: true })
const writable = await marker.createWritable()
await writable.write(JSON.stringify({ operation: 'fillet', provider: nativeCapabilities.providerId }))
await writable.close()
const markerText = await (await marker.getFile()).text()
const records = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-fillet-history', operation: 'fillet', radius: 0.4, inputs: [{ objectId: 'Base', shape: base }], result })
const relations = { modified: records.filter((record) => record.relation === 'modified').length, generated: records.filter((record) => record.relation === 'generated').length, deleted: records.filter((record) => record.relation === 'deleted').length }
report.capabilities = capabilities
report.nativeCapabilities = nativeCapabilities
report.history = { recordCount: records.length, relations, sourceObjects: [...new Set(records.map((record) => record.sourceObjectId))] }
report.result = { structuralValid: quality.structuralValid, solids: quality.solids, volume: mass.volume }
report.opfs = { markerCreated: true, markerSuite: JSON.parse(markerText).operation, markerRemoved: false }
await markerDirectory.removeEntry(markerName)
report.opfs.markerRemoved = true
await runtime.release(result)
result = undefined
await runtime.release(base)
base = undefined
report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
report.status = report.crossOriginIsolated && capabilities.status === 'ready' && nativeCapabilities.availability === 'available' && nativeCapabilities.operations.includes('fillet') && records.length > 0 && relations.modified > 0 && relations.generated > 0 && relations.deleted > 0 && quality.structuralValid && quality.solids === 1 && mass.volume > 0 && mass.volume < 216 && report.opfs.markerSuite === 'fillet' && report.opfs.markerRemoved && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? 'pass' : 'failed'
} catch (error) {
report.status = 'failed'
report.error = error instanceof Error ? `${error.name}: ${error.message}` : String(error)
} finally {
if (result) await runtime?.release(result).catch(() => {})
if (base) await runtime?.release(base).catch(() => {})
provider?.dispose()
runtime?.dispose()
}
output.textContent = JSON.stringify(report, null, 2)
await fetch('/__native-fillet-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome native Fillet history harness failed.')
</script>

View File

@@ -0,0 +1,74 @@
<!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>

View File

@@ -0,0 +1,48 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native OCCT 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 {
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-history', documentVersion: 1 }
const base = await runtime.createBox({ ...context, width: 10, length: 10, height: 10 })
const tool = await runtime.createBox({ ...context, width: 6, length: 6, height: 6, center: [5, 5, 5], originOnCenter: true })
const result = await runtime.cut({ ...context, base, tools: [tool] })
const markerDirectory = await navigator.storage.getDirectory()
const marker = await markerDirectory.getFileHandle('native-history-chrome-marker.json', { create: true })
const writable = await marker.createWritable()
await writable.write(JSON.stringify({ operation: 'cut', provider: nativeCapabilities.providerId }))
await writable.close()
const records = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-history-cut', operation: 'cut', inputs: [{ objectId: 'Base', shape: base }, { objectId: 'Tool', shape: tool }], result })
report.capabilities = capabilities
report.nativeCapabilities = nativeCapabilities
report.history = { recordCount: records.length, relations: Object.fromEntries(records.reduce((counts, record) => counts.set(record.relation, (counts.get(record.relation) || 0) + 1), new Map())) }
await runtime.release(result)
await runtime.release(tool)
await runtime.release(base)
report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
await markerDirectory.removeEntry('native-history-chrome-marker.json').catch(() => {})
report.status = report.crossOriginIsolated && capabilities.status === 'ready' && nativeCapabilities.availability === 'available' && nativeCapabilities.operations.includes('cut') && records.length > 0 && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? '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-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome native history harness failed.')
</script>

View File

@@ -0,0 +1,28 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native Hole 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', executionContext: 'geometry-and-history-workers', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
let runtime, provider, base, tool, result
try {
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-hole', documentVersion: 1 }
base = await runtime.createBox({ ...context, width: 10, length: 10, height: 10, center: [0, 0, 0], originOnCenter: true })
tool = await runtime.createCylinder({ ...context, radius: 1, height: 10, center: [0, 0, -5], direction: [0, 0, 1] })
result = await runtime.cut({ ...context, base, tools: [tool] })
const [quality, mass] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result)])
const markerDirectory = await navigator.storage.getDirectory(); const markerName = 'native-hole-history-chrome-marker.json'; const marker = await markerDirectory.getFileHandle(markerName, { create: true }); const writable = await marker.createWritable(); await writable.write(JSON.stringify({ operation: 'hole', provider: nativeCapabilities.providerId })); await writable.close(); const markerText = await (await marker.getFile()).text()
const records = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-hole-history', operation: 'hole', radius: 1, depth: 10, position: [0, 0, -5], direction: [0, 0, 1], inputs: [{ objectId: 'Base', shape: base }], result })
const stageCaptures = records.stageCaptures || []
const relations = { modified: records.filter((record) => record.relation === 'modified').length, generated: records.filter((record) => record.relation === 'generated').length, deleted: records.filter((record) => record.relation === 'deleted').length }
report.capabilities = capabilities; report.nativeCapabilities = nativeCapabilities; report.history = { recordCount: records.length, relations, sourceObjects: [...new Set(records.map((record) => record.sourceObjectId))], 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 })), sourceStageIds: [...new Set(records.map((record) => record.sourceStageId).filter(Boolean))], resultStageIds: [...new Set(records.map((record) => record.resultStageId).filter(Boolean))] }; report.result = { structuralValid: quality.structuralValid, solids: quality.solids, volume: mass.volume }; report.opfs = { markerCreated: true, markerSuite: JSON.parse(markerText).operation, markerRemoved: false }; await markerDirectory.removeEntry(markerName); report.opfs.markerRemoved = true
await runtime.release(result); result = undefined; await runtime.release(tool); tool = undefined; await runtime.release(base); base = undefined; report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
report.status = report.crossOriginIsolated && capabilities.status === 'ready' && nativeCapabilities.availability === 'available' && nativeCapabilities.operations.includes('hole') && records.length > 0 && relations.modified > 0 && relations.deleted > 0 && stageCaptures.length === 2 && stageCaptures[0].operation === 'hole' && stageCaptures[0].inputObjectIds.length === 0 && stageCaptures[0].topology.entries.length > 0 && stageCaptures[1].operation === 'cut' && stageCaptures[1].inputObjectIds.join(',') === `Base,${stageCaptures[0].resultObjectId}` && stageCaptures[1].topology.entries.length > 0 && records.every((record) => record.resultStageId === stageCaptures[1].stageId) && quality.structuralValid && quality.solids === 1 && mass.volume > 0 && mass.volume < 1000 && report.opfs.markerSuite === 'hole' && report.opfs.markerRemoved && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? 'pass' : 'failed'
} catch (error) { report.status = 'failed'; report.error = error instanceof Error ? `${error.name}: ${error.message}` : String(error) } finally { if (result) await runtime?.release(result).catch(() => {}); if (tool) await runtime?.release(tool).catch(() => {}); if (base) await runtime?.release(base).catch(() => {}); provider?.dispose(); runtime?.dispose() }
output.textContent = JSON.stringify(report, null, 2); await fetch('/__native-hole-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) }); if (report.status !== 'pass') throw new Error(report.error || 'Chrome native Hole history harness failed.')
</script>

View File

@@ -0,0 +1,5 @@
<!doctype html><meta charset="utf-8"><title>Chrome native LinearPattern history</title><pre id="result">running</pre><script type="module">
import { BitbybitGeometryRuntime } from '/src/facade/geometryRuntime.ts'; import { NativeOcctHistoryWorkerProvider } from '/src/facade/nativeHistoryWorkerClient.ts'; import { NativeOcctHistoryCoordinator } from '/src/facade/nativeHistoryProtocol.ts'
const output=document.querySelector('#result'); const report={schemaVersion:1,browserId:'chrome',crossOriginIsolated:globalThis.crossOriginIsolated===true,status:'running'}; let runtime,provider,base,copy,result
try { 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-linear-pattern',documentVersion:1}; base=await runtime.createBox({...context,width:2,length:2,height:2}); copy=await runtime.applyPlacement({...context,shape:base,placement:{translation:[1,0,0],rotationAxis:[0,0,1],rotationAngle:0}}); result=await runtime.union({...context,shapes:[base,copy]}); const [quality,mass,baseStep]=await Promise.all([runtime.qualityReport(result),runtime.massProperties(result),runtime.exportStep(base,'Base.step')]); const execution=await new NativeOcctHistoryCoordinator(()=>1).capture(provider,{...context,operationId:'linear-pattern-history',operation:'linear-pattern',objectStep:baseStep.text,direction:[1,0,0]}); if(execution.status!=='completed'||!execution.response)throw new Error(`Native pattern history ${execution.status}`); const records=execution.response.history.records; const relations={modified:records.filter(r=>r.relation==='modified').length,generated:records.filter(r=>r.relation==='generated').length,deleted:records.filter(r=>r.relation==='deleted').length}; const directory=await navigator.storage.getDirectory(); const name='native-linear-pattern-history-marker.json'; const marker=await directory.getFileHandle(name,{create:true}); const writable=await marker.createWritable(); await writable.write(JSON.stringify({operation:'linear-pattern'})); await writable.close(); const markerSuite=JSON.parse(await (await marker.getFile()).text()).operation; await directory.removeEntry(name); report.capabilities=capabilities; report.nativeCapabilities=nativeCapabilities; report.history={recordCount:records.length,relations,sourceObjects:[...new Set(records.map(r=>r.source==='object'?'Base':'Base'))]}; report.result={bitbybitStructuralValid:quality.structuralValid,bitbybitSolids:quality.solids,bitbybitVolume:mass.volume,nativeStructuralValid:execution.response.history.summary.isValid,nativeSolids:execution.response.history.summary.solids,nativeVolume:execution.response.history.summary.volume}; report.opfs={markerSuite,markerRemoved:true}; await runtime.release(result); result=undefined; await runtime.release(copy); copy=undefined; await runtime.release(base); base=undefined; report.afterRelease={shapeCount:runtime.capabilities().shapeCount,kernelReferenceCount:runtime.capabilities().kernelReferenceCount}; report.status=report.crossOriginIsolated&&nativeCapabilities.operations.includes('linear-pattern')&&records.length>0&&quality.solids===1&&Math.abs(mass.volume-12)<1e-7&&report.result.nativeStructuralValid&&report.result.nativeSolids===1&&Math.abs(report.result.nativeVolume-12)<1e-7&&markerSuite==='linear-pattern'&&report.afterRelease.shapeCount===0&&report.afterRelease.kernelReferenceCount===0?'pass':'failed' } catch(error){report.status='failed';report.error=error instanceof Error?error.stack||error.message:String(error)} finally {if(result)await runtime?.release(result).catch(()=>{});if(copy)await runtime?.release(copy).catch(()=>{});if(base)await runtime?.release(base).catch(()=>{});provider?.dispose();runtime?.dispose()} output.textContent=JSON.stringify(report,null,2); await fetch('/__native-linear-pattern-history-report',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(report)}); if(report.status!=='pass')throw new Error(report.error||'failed')
</script>

View File

@@ -0,0 +1,36 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native Loft history</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, provider, first, second, result
try {
const [firstStep, secondStep] = await Promise.all([
fetch('/native-loft-first.step').then((response) => response.ok ? response.text() : Promise.reject(new Error(`First Loft fixture failed: ${response.status}`))),
fetch('/native-loft-second.step').then((response) => response.ok ? response.text() : Promise.reject(new Error(`Second Loft 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-loft', documentVersion: 1 }
first = await runtime.importShape({ ...context, format: 'step', text: firstStep })
second = await runtime.importShape({ ...context, format: 'step', text: secondStep })
result = await runtime.loft({ ...context, sections: [
{ outer: [[0, 0, 0], [2, 0, 0], [2, 2, 0], [0, 2, 0]] },
{ outer: [[0, 0, 5], [2, 0, 5], [2, 2, 5], [0, 2, 5]] },
], mode: 'standalone', ruled: false, closed: false })
const records = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-loft-history', operation: 'loft', inputs: [{ objectId: 'FirstSection', shape: first }, { objectId: 'SecondSection', shape: second }], ruled: false, result })
const [quality, mass] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result)])
const relations = { modified: records.filter((entry) => entry.relation === 'modified').length, generated: records.filter((entry) => entry.relation === 'generated').length, deleted: records.filter((entry) => entry.relation === 'deleted').length }
const directory = await navigator.storage.getDirectory(); const markerName = 'native-loft-history-marker.json'; const marker = await directory.getFileHandle(markerName, { create: true }); const writable = await marker.createWritable(); await writable.write(JSON.stringify({ operation: 'loft', sections: 2, ruled: false })); await writable.close(); const markerPayload = JSON.parse(await (await marker.getFile()).text()); await directory.removeEntry(markerName)
report.capabilities = capabilities; report.nativeCapabilities = nativeCapabilities; report.history = { recordCount: records.length, relations, sourceObjects: [...new Set(records.map((entry) => entry.sourceObjectId))].sort() }; report.result = { bitbybitStructuralValid: quality.structuralValid, bitbybitSolids: quality.solids, bitbybitVolume: mass.volume }; report.opfs = { markerPayload, markerRemoved: true }
await runtime.release(result); result = undefined; await runtime.release(second); second = undefined; await runtime.release(first); first = undefined; report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
report.status = report.crossOriginIsolated && capabilities.status === 'ready' && nativeCapabilities.operations.includes('loft') && records.length > 0 && relations.generated > 0 && report.history.sourceObjects.includes('FirstSection') && report.history.sourceObjects.includes('SecondSection') && quality.solids === 1 && Math.abs(mass.volume - 20) < 1e-7 && markerPayload.sections === 2 && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? 'pass' : 'failed'
} catch (error) { report.status = 'failed'; report.error = error instanceof Error ? error.stack || error.message : String(error) }
finally { if (result) await runtime?.release(result).catch(() => {}); if (second) await runtime?.release(second).catch(() => {}); if (first) await runtime?.release(first).catch(() => {}); provider?.dispose(); runtime?.dispose() }
output.textContent = JSON.stringify(report, null, 2); await fetch('/__native-loft-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) }); if (report.status !== 'pass') throw new Error(report.error || 'failed')
</script>

View File

@@ -0,0 +1,31 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native Mirrored history</title>
<pre id="result">running</pre>
<script type="module">
import { BitbybitGeometryRuntime } from '/src/facade/geometryRuntime.ts'
import { NativeOcctHistoryWorkerProvider } from '/src/facade/nativeHistoryWorkerClient.ts'
import { NativeOcctHistoryCoordinator } from '/src/facade/nativeHistoryProtocol.ts'
const output = document.querySelector('#result')
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
let runtime, provider, base, copy, result
try {
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-mirrored', documentVersion: 1 }
base = await runtime.createBox({ ...context, width: 2, length: 1, height: 1, center: [0.5, 0, 0], originOnCenter: true })
copy = await runtime.mirror({ ...context, shape: base, origin: [0, 0, 0], normal: [1, 0, 0] })
result = await runtime.union({ ...context, shapes: [base, copy] })
const [quality, mass, baseStep] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result), runtime.exportStep(base, 'Base.step')])
const execution = await new NativeOcctHistoryCoordinator(() => 1).capture(provider, { ...context, operationId: 'mirrored-history', operation: 'mirrored', objectStep: baseStep.text, axisOrigin: [0, 0, 0], direction: [1, 0, 0] })
if (execution.status !== 'completed' || !execution.response) throw new Error(`Native Mirrored history ${execution.status}.`)
const records = execution.response.history.records
const relations = { modified: records.filter((entry) => entry.relation === 'modified').length, generated: records.filter((entry) => entry.relation === 'generated').length, deleted: records.filter((entry) => entry.relation === 'deleted').length }
const directory = await navigator.storage.getDirectory(); const markerName = 'native-mirrored-history-marker.json'; const marker = await directory.getFileHandle(markerName, { create: true }); const writable = await marker.createWritable(); await writable.write(JSON.stringify({ operation: 'mirrored' })); await writable.close(); const markerSuite = JSON.parse(await (await marker.getFile()).text()).operation; await directory.removeEntry(markerName)
report.capabilities = capabilities; report.nativeCapabilities = nativeCapabilities; report.history = { recordCount: records.length, relations, sourceObjects: ['Base'] }; report.result = { bitbybitStructuralValid: quality.structuralValid, bitbybitSolids: quality.solids, bitbybitVolume: mass.volume, nativeStructuralValid: execution.response.history.summary.isValid, nativeSolids: execution.response.history.summary.solids, nativeVolume: execution.response.history.summary.volume }; report.opfs = { markerSuite, markerRemoved: true }
await runtime.release(result); result = undefined; await runtime.release(copy); copy = undefined; await runtime.release(base); base = undefined; report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
report.status = report.crossOriginIsolated && nativeCapabilities.operations.includes('mirrored') && records.length > 0 && quality.solids === 1 && Math.abs(mass.volume - 3) < 1e-7 && report.result.nativeStructuralValid && report.result.nativeSolids === 1 && Math.abs(report.result.nativeVolume - 3) < 1e-7 && markerSuite === 'mirrored' && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? 'pass' : 'failed'
} catch (error) { report.status = 'failed'; report.error = error instanceof Error ? error.stack || error.message : String(error) }
finally { if (result) await runtime?.release(result).catch(() => {}); if (copy) await runtime?.release(copy).catch(() => {}); if (base) await runtime?.release(base).catch(() => {}); provider?.dispose(); runtime?.dispose() }
output.textContent = JSON.stringify(report, null, 2); await fetch('/__native-mirrored-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) }); if (report.status !== 'pass') throw new Error(report.error || 'failed')
</script>

View File

@@ -0,0 +1,36 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native MultiTransform history</title>
<pre id="result">running</pre>
<script type="module">
import { BitbybitGeometryRuntime } from '/src/facade/geometryRuntime.ts'
import { NativeOcctHistoryWorkerProvider } from '/src/facade/nativeHistoryWorkerClient.ts'
import { NativeOcctHistoryCoordinator } from '/src/facade/nativeHistoryProtocol.ts'
const output = document.querySelector('#result')
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
let runtime, provider, base, copy, mirrorBase, mirrorCopy, result
try {
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-multi-transform', documentVersion: 1 }
base = await runtime.createBox({ ...context, width: 2, length: 1, height: 1, center: [0.5, 0, 0], originOnCenter: true })
copy = await runtime.applyPlacement({ ...context, shape: base, placement: { translation: [1, 0, 0], rotationAxis: [0, 0, 1], rotationAngle: 0 } })
mirrorBase = await runtime.mirror({ ...context, shape: base, origin: [0, 0, 0], normal: [1, 0, 0] })
mirrorCopy = await runtime.mirror({ ...context, shape: copy, origin: [0, 0, 0], normal: [1, 0, 0] })
result = await runtime.union({ ...context, shapes: [base, copy, mirrorBase, mirrorCopy] })
const [quality, mass, baseStep] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result), runtime.exportStep(base, 'Base.step')])
const transforms = [{ type: 'linear', direction: [1, 0, 0] }, { type: 'mirrored', axisOrigin: [0, 0, 0], direction: [1, 0, 0] }]
const stagedRecords = await runtime.topologyHistory({ ...context, operationId: 'multi-transform-staged-history', operation: 'multi-transform', transforms, inputs: [{ objectId: 'Base', shape: base }], stages: [{ stageId: 'MultiTransform:stage:0', operation: 'multi-transform', inputObjectIds: ['Base'], resultObjectId: 'MultiTransform', ordinal: 0 }], result })
const stageCaptures = stagedRecords.stageCaptures || []
const execution = await new NativeOcctHistoryCoordinator(() => 1).capture(provider, { ...context, operationId: 'multi-transform-history', operation: 'multi-transform', objectStep: baseStep.text, transforms })
if (execution.status !== 'completed' || !execution.response) throw new Error(`Native MultiTransform history ${execution.status}.`)
const records = execution.response.history.records
const relations = { modified: records.filter((entry) => entry.relation === 'modified').length, generated: records.filter((entry) => entry.relation === 'generated').length, deleted: records.filter((entry) => entry.relation === 'deleted').length }
const directory = await navigator.storage.getDirectory(); const markerName = 'native-multi-transform-history-marker.json'; const marker = await directory.getFileHandle(markerName, { create: true }); const writable = await marker.createWritable(); await writable.write(JSON.stringify({ operation: 'multi-transform', transformKinds: transforms.map((step) => step.type) })); await writable.close(); const markerPayload = JSON.parse(await (await marker.getFile()).text()); await directory.removeEntry(markerName)
report.capabilities = capabilities; report.nativeCapabilities = nativeCapabilities; report.history = { recordCount: records.length, relations, sourceObjects: ['Base'], transformKinds: transforms.map((step) => step.type), stagedRecordCount: stagedRecords.length, 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 })), finalRecordLineage: { records: stagedRecords.length, sourceObjectIds: [...new Set(stagedRecords.map((entry) => entry.sourceObjectId))], sourceStageIds: [...new Set(stagedRecords.map((entry) => entry.sourceStageId))], resultStageIds: [...new Set(stagedRecords.map((entry) => entry.resultStageId))] } }; report.result = { bitbybitStructuralValid: quality.structuralValid, bitbybitSolids: quality.solids, bitbybitVolume: mass.volume, nativeStructuralValid: execution.response.history.summary.isValid, nativeSolids: execution.response.history.summary.solids, nativeVolume: execution.response.history.summary.volume }; report.opfs = { markerPayload, markerRemoved: true }
await runtime.release(result); result = undefined; await runtime.release(mirrorCopy); mirrorCopy = undefined; await runtime.release(mirrorBase); mirrorBase = undefined; await runtime.release(copy); copy = undefined; await runtime.release(base); base = undefined; report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
report.status = report.crossOriginIsolated && nativeCapabilities.operations.includes('multi-transform') && records.length > 0 && relations.modified > 0 && relations.generated > 0 && stageCaptures.length === 2 && stageCaptures[0].operation === 'linear-pattern' && stageCaptures[1].operation === 'mirrored' && stageCaptures[0].inputObjectIds[0] === 'Base' && stageCaptures[1].inputObjectIds[0] === stageCaptures[0].resultObjectId && stageCaptures[1].resultObjectId === 'MultiTransform' && stageCaptures.every((stage) => stage.topology.entries.length > 0 && stage.records.length > 0) && stagedRecords.every((entry) => entry.sourceStageId === stageCaptures[0].stageId && entry.resultStageId === stageCaptures[1].stageId) && quality.solids === 1 && Math.abs(mass.volume - 5) < 1e-7 && report.result.nativeStructuralValid && report.result.nativeSolids === 1 && Math.abs(report.result.nativeVolume - 5) < 1e-7 && markerPayload.transformKinds.join(',') === 'linear,mirrored' && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? 'pass' : 'failed'
} catch (error) { report.status = 'failed'; report.error = error instanceof Error ? error.stack || error.message : String(error) }
finally { if (result) await runtime?.release(result).catch(() => {}); if (mirrorCopy) await runtime?.release(mirrorCopy).catch(() => {}); if (mirrorBase) await runtime?.release(mirrorBase).catch(() => {}); if (copy) await runtime?.release(copy).catch(() => {}); if (base) await runtime?.release(base).catch(() => {}); provider?.dispose(); runtime?.dispose() }
output.textContent = JSON.stringify(report, null, 2); await fetch('/__native-multi-transform-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) }); if (report.status !== 'pass') throw new Error(report.error || 'failed')
</script>

View File

@@ -0,0 +1,38 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native Pad history harness</title>
<pre id="status">loading</pre>
<script type="module">
import { NativeOcctHistoryCoordinator } from '/src/facade/nativeHistoryProtocol.ts'
import { NativeOcctHistoryWorkerProvider } from '/src/facade/nativeHistoryWorkerClient.ts'
const report = { schemaVersion: 1, browserId: 'chrome', executionContext: 'dedicated-worker', crossOriginIsolated, status: 'failed' }
let provider
try {
const profileStep = await fetch('/native-pad-profile.step').then((response) => response.ok ? response.text() : Promise.reject(new Error(`Pad profile fixture failed: ${response.status}`)))
provider = new NativeOcctHistoryWorkerProvider()
const capabilities = await provider.initialize()
const execution = await new NativeOcctHistoryCoordinator(() => 1).capture(provider, {
documentId: 'chrome-native-pad', documentVersion: 1, operationId: 'pad-history', operation: 'pad', objectStep: profileStep, direction: [0, 0, 5],
})
const history = execution.response?.history
const records = history?.records || []
const crossKind = records.filter((record) => record.resultKind && record.resultKind !== record.kind)
const summary = history?.summary
const validRelations = records.every((record) => ['modified', 'generated', 'deleted'].includes(record.relation) && Number.isSafeInteger(record.sourceIndex) && record.sourceIndex >= 0)
const profileKinds = new Set(records.map((record) => record.kind))
const shapeSummary = summary && typeof summary === 'object' ? summary : null
Object.assign(report, {
status: capabilities.availability === 'available' && capabilities.operations.includes('pad') && execution.status === 'completed' && records.length > 0 && validRelations && crossKind.length > 0 && profileKinds.has('vertex') && profileKinds.has('edge') && profileKinds.has('face') && shapeSummary?.isValid === true && shapeSummary?.solids === 1 && Math.abs(shapeSummary.volume - 30) <= 1e-7 ? 'pass' : 'failed',
nativeCapabilities: capabilities,
execution: { status: execution.status, recordCount: records.length, crossKindRecords: crossKind.length, relations: { modified: records.filter((record) => record.relation === 'modified').length, generated: records.filter((record) => record.relation === 'generated').length, deleted: records.filter((record) => record.relation === 'deleted').length }, profileKinds: [...profileKinds], summary: shapeSummary },
})
} catch (error) {
report.error = error instanceof Error ? `${error.name}: ${error.message}` : String(error)
} finally {
provider?.dispose()
report.workerDisposed = true
}
document.querySelector('#status').textContent = JSON.stringify(report, null, 2)
await fetch('/__native-pad-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
</script>

View File

@@ -0,0 +1,33 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native Pipe history</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, provider, profile, spine, result
try {
const [profileStep, spineStep] = await Promise.all([
fetch('/native-pipe-profile.step').then((response) => response.ok ? response.text() : Promise.reject(new Error(`Pipe profile fixture failed: ${response.status}`))),
fetch('/native-pipe-spine.step').then((response) => response.ok ? response.text() : Promise.reject(new Error(`Pipe spine 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-pipe', documentVersion: 1 }
profile = await runtime.importShape({ ...context, format: 'step', text: profileStep })
spine = await runtime.importShape({ ...context, format: 'step', text: spineStep })
result = await runtime.pipe({ ...context, profile: { outer: [[0, 0, 0], [2, 0, 0], [2, 2, 0], [0, 2, 0]] }, path: [[0, 0, 0], [0, 0, 5]], mode: 'standalone' })
const records = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-pipe-history', operation: 'pipe', inputs: [{ objectId: 'Profile', shape: profile }, { objectId: 'Spine', shape: spine }], result })
const [quality, mass] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result)])
const relations = { modified: records.filter((entry) => entry.relation === 'modified').length, generated: records.filter((entry) => entry.relation === 'generated').length, deleted: records.filter((entry) => entry.relation === 'deleted').length }
const directory = await navigator.storage.getDirectory(); const markerName = 'native-pipe-history-marker.json'; const marker = await directory.getFileHandle(markerName, { create: true }); const writable = await marker.createWritable(); await writable.write(JSON.stringify({ operation: 'pipe', profiles: 1, spineEdges: 1 })); await writable.close(); const markerPayload = JSON.parse(await (await marker.getFile()).text()); await directory.removeEntry(markerName)
report.capabilities = capabilities; report.nativeCapabilities = nativeCapabilities; report.history = { recordCount: records.length, relations, sourceObjects: [...new Set(records.map((entry) => entry.sourceObjectId))].sort() }; report.result = { bitbybitStructuralValid: quality.structuralValid, bitbybitSolids: quality.solids, bitbybitVolume: mass.volume }; report.opfs = { markerPayload, markerRemoved: true }
await runtime.release(result); result = undefined; await runtime.release(spine); spine = undefined; await runtime.release(profile); profile = undefined; report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
report.status = report.crossOriginIsolated && capabilities.status === 'ready' && nativeCapabilities.operations.includes('pipe') && records.length > 0 && relations.modified > 0 && relations.generated > 0 && report.history.sourceObjects.includes('Profile') && report.history.sourceObjects.includes('Spine') && quality.solids === 1 && Math.abs(mass.volume - 20) < 1e-7 && markerPayload.spineEdges === 1 && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? 'pass' : 'failed'
} catch (error) { report.status = 'failed'; report.error = error instanceof Error ? error.stack || error.message : String(error) }
finally { if (result) await runtime?.release(result).catch(() => {}); if (spine) await runtime?.release(spine).catch(() => {}); if (profile) await runtime?.release(profile).catch(() => {}); provider?.dispose(); runtime?.dispose() }
output.textContent = JSON.stringify(report, null, 2); await fetch('/__native-pipe-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) }); if (report.status !== 'pass') throw new Error(report.error || 'failed')
</script>

View File

@@ -0,0 +1,70 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native Pocket 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-pocket-base.step').then((response) => response.ok ? response.text() : Promise.reject(new Error(`Pocket base fixture failed: ${response.status}`))),
fetch('/native-pocket-profile.step').then((response) => response.ok ? response.text() : Promise.reject(new Error(`Pocket 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-pocket', documentVersion: 1 }
const base = await runtime.importShape({ ...context, format: 'step', text: baseStep })
const profile = await runtime.importShape({ ...context, format: 'step', text: profileStep })
const result = await runtime.pocket({ ...context, base, profile: { outer: [[0, 0, 0], [2, 0, 0], [2, 3, 0], [0, 3, 0]] }, length: 5, direction: [0, 0, 1], reversed: false, symmetricToPlane: false })
const markerDirectory = await navigator.storage.getDirectory()
const marker = await markerDirectory.getFileHandle('native-pocket-history-chrome-marker.json', { create: true })
const writable = await marker.createWritable()
await writable.write(JSON.stringify({ operation: 'pocket', provider: nativeCapabilities.providerId }))
await writable.close()
const records = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-pocket-history', operation: 'pocket', inputs: [{ objectId: 'Base', shape: base }, { objectId: 'Profile', shape: profile }], direction: [0, 0, 5], result })
const twoSidedBase = await runtime.createBox({ ...context, width: 10, length: 8, height: 10, center: [0, 0, 0], originOnCenter: true })
const firstSideTool = await runtime.pad({ ...context, profile: { outer: [[0, 0, 0], [2, 0, 0], [2, 3, 0], [0, 3, 0]] }, length: 4, direction: [0, 0, 1] })
const secondSideTool = await runtime.pad({ ...context, profile: { outer: [[0, 0, 0], [2, 0, 0], [2, 3, 0], [0, 3, 0]] }, length: 4, direction: [0, 0, 1], reversed: true })
const fusedTool = await runtime.union({ ...context, shapes: [firstSideTool, secondSideTool] })
const twoSidedResult = await runtime.cut({ ...context, base: twoSidedBase, tools: [fusedTool] })
const twoSidedRecords = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-pocket-two-sided-history', operation: 'pocket', inputs: [{ objectId: 'BaseTwoSided', shape: twoSidedBase }, { objectId: 'Profile', shape: profile }], stages: [{ stageId: 'Pocket:stage:0', operation: 'pocket', inputObjectIds: ['BaseTwoSided', 'Profile'], resultObjectId: 'PocketTwoSided', ordinal: 0 }], featureSides: [{ direction: [0, 0, 4] }, { direction: [0, 0, -4] }], direction: [0, 0, 4], result: twoSidedResult })
const twoSidedStages = twoSidedRecords.stageCaptures || []
const twoSidedQuality = await runtime.qualityReport(twoSidedResult)
const stageCaptures = records.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.twoSidedHistory = { recordCount: twoSidedRecords.length, structuralValid: twoSidedQuality.structuralValid, solids: twoSidedQuality.solids, sourceObjects: [...new Set(twoSidedStages.flatMap((stage) => stage.records.map((record) => record.sourceObjectId)))].sort(), stages: twoSidedStages.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 })) }
await runtime.release(twoSidedResult)
await runtime.release(fusedTool)
await runtime.release(secondSideTool)
await runtime.release(firstSideTool)
await runtime.release(twoSidedBase)
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('native-pocket-history-chrome-marker.json').catch(() => {})
report.status = report.crossOriginIsolated && capabilities.status === 'ready' && nativeCapabilities.availability === 'available' && nativeCapabilities.operations.includes('pocket') && nativeCapabilities.operations.includes('pad') && nativeCapabilities.operations.includes('fuse') && nativeCapabilities.operations.includes('cut') && records.length > 0 && modified > 0 && generated + deleted > 0 && stageCaptures.length === 2 && stageCaptures[0].operation === 'pad' && 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) && twoSidedRecords.length > 0 && twoSidedQuality.structuralValid && twoSidedQuality.solids === 1 && twoSidedStages.length === 4 && twoSidedStages[0].operation === 'pad' && twoSidedStages[0].inputObjectIds.join(',') === 'Profile' && twoSidedStages[0].topology.entries.length > 0 && twoSidedStages[1].operation === 'pad' && twoSidedStages[1].inputObjectIds.join(',') === 'Profile' && twoSidedStages[1].topology.entries.length > 0 && twoSidedStages[2].operation === 'fuse' && twoSidedStages[2].inputObjectIds.join(',') === `${twoSidedStages[0].resultObjectId},${twoSidedStages[1].resultObjectId}` && twoSidedStages[2].topology.entries.length > 0 && twoSidedStages[3].operation === 'cut' && twoSidedStages[3].inputObjectIds.join(',') === `BaseTwoSided,${twoSidedStages[2].resultObjectId}` && twoSidedStages[3].resultObjectId === 'PocketTwoSided' && twoSidedStages[3].topology.entries.length > 0 && report.twoSidedHistory.sourceObjects.includes('BaseTwoSided') && report.twoSidedHistory.sourceObjects.includes('Profile') && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? '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-pocket-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome native Pocket history harness failed.')
</script>

View File

@@ -0,0 +1,31 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native PolarPattern history</title>
<pre id="result">running</pre>
<script type="module">
import { BitbybitGeometryRuntime } from '/src/facade/geometryRuntime.ts'
import { NativeOcctHistoryWorkerProvider } from '/src/facade/nativeHistoryWorkerClient.ts'
import { NativeOcctHistoryCoordinator } from '/src/facade/nativeHistoryProtocol.ts'
const output = document.querySelector('#result')
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
let runtime, provider, base, copy, result
try {
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-polar-pattern', documentVersion: 1 }
base = await runtime.createBox({ ...context, width: 2, length: 1, height: 1, center: [0, 0, 0], originOnCenter: true })
copy = await runtime.applyPlacement({ ...context, shape: base, placement: { translation: [0, 0, 0], rotationAxis: [0, 0, 1], rotationAngle: 90 } })
result = await runtime.union({ ...context, shapes: [base, copy] })
const [quality, mass, baseStep] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result), runtime.exportStep(base, 'Base.step')])
const execution = await new NativeOcctHistoryCoordinator(() => 1).capture(provider, { ...context, operationId: 'polar-pattern-history', operation: 'polar-pattern', objectStep: baseStep.text, axisOrigin: [0, 0, 0], direction: [0, 0, 1], angle: 90 })
if (execution.status !== 'completed' || !execution.response) throw new Error(`Native PolarPattern history ${execution.status}.`)
const records = execution.response.history.records
const relations = { modified: records.filter((entry) => entry.relation === 'modified').length, generated: records.filter((entry) => entry.relation === 'generated').length, deleted: records.filter((entry) => entry.relation === 'deleted').length }
const directory = await navigator.storage.getDirectory(); const markerName = 'native-polar-pattern-history-marker.json'; const marker = await directory.getFileHandle(markerName, { create: true }); const writable = await marker.createWritable(); await writable.write(JSON.stringify({ operation: 'polar-pattern' })); await writable.close(); const markerSuite = JSON.parse(await (await marker.getFile()).text()).operation; await directory.removeEntry(markerName)
report.capabilities = capabilities; report.nativeCapabilities = nativeCapabilities; report.history = { recordCount: records.length, relations, sourceObjects: ['Base'] }; report.result = { bitbybitStructuralValid: quality.structuralValid, bitbybitSolids: quality.solids, bitbybitVolume: mass.volume, nativeStructuralValid: execution.response.history.summary.isValid, nativeSolids: execution.response.history.summary.solids, nativeVolume: execution.response.history.summary.volume }; report.opfs = { markerSuite, markerRemoved: true }
await runtime.release(result); result = undefined; await runtime.release(copy); copy = undefined; await runtime.release(base); base = undefined; report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
report.status = report.crossOriginIsolated && nativeCapabilities.operations.includes('polar-pattern') && records.length > 0 && quality.solids === 1 && Math.abs(mass.volume - 3) < 1e-7 && report.result.nativeStructuralValid && report.result.nativeSolids === 1 && Math.abs(report.result.nativeVolume - 3) < 1e-7 && markerSuite === 'polar-pattern' && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? 'pass' : 'failed'
} catch (error) { report.status = 'failed'; report.error = error instanceof Error ? error.stack || error.message : String(error) }
finally { if (result) await runtime?.release(result).catch(() => {}); if (copy) await runtime?.release(copy).catch(() => {}); if (base) await runtime?.release(base).catch(() => {}); provider?.dispose(); runtime?.dispose() }
output.textContent = JSON.stringify(report, null, 2); await fetch('/__native-polar-pattern-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) }); if (report.status !== 'pass') throw new Error(report.error || 'failed')
</script>

View File

@@ -0,0 +1,61 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome native Revolution 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 profileStep = await fetch('/native-revolution-profile.step').then((response) => response.ok ? response.text() : Promise.reject(new Error(`Revolution 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-revolution', documentVersion: 1 }
const profile = await runtime.importShape({ ...context, format: 'step', text: profileStep })
const planarProfile = { outer: [[0, 0, 0], [2, 0, 0], [2, 3, 0], [0, 3, 0]] }
const result = await runtime.revolution({ ...context, profile: planarProfile, axisOrigin: [-1, 0, 0], axisDirection: [0, 1, 0], angle: 360 })
const [quality, mass] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result)])
const markerDirectory = await navigator.storage.getDirectory()
const marker = await markerDirectory.getFileHandle('native-revolution-history-chrome-marker.json', { create: true })
const writable = await marker.createWritable()
await writable.write(JSON.stringify({ operation: 'revolution', provider: nativeCapabilities.providerId }))
await writable.close()
const records = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-revolution-history', operation: 'revolution', inputs: [{ objectId: 'Profile', shape: profile }], axisOrigin: [-1, 0, 0], direction: [0, 1, 0], angle: 360, result })
const twoSidedResult = await runtime.revolution({ ...context, profile: planarProfile, axisOrigin: [-1, 0, 0], axisDirection: [0, 1, 0], angle: 180, profileRotationAngle: -60 })
const twoSidedRecords = await runtime.topologyHistory({ ...context, operationId: 'chrome-native-revolution-two-sided-history', operation: 'revolution', inputs: [{ objectId: 'Profile', shape: profile }], stages: [{ stageId: 'Revolution:stage:0', operation: 'revolution', inputObjectIds: ['Profile'], resultObjectId: 'RevolutionTwoSided', ordinal: 0 }], featureSides: [{ direction: [0, 1, 0], angle: 120 }, { direction: [0, -1, 0], angle: 60 }], axisOrigin: [-1, 0, 0], direction: [0, 1, 0], angle: 120, result: twoSidedResult })
const twoSidedStages = twoSidedRecords.stageCaptures || []
const [twoSidedQuality, twoSidedMass] = await Promise.all([runtime.qualityReport(twoSidedResult), runtime.massProperties(twoSidedResult)])
const twoSidedUnexplainedErrors = (twoSidedQuality.structuralIssues || []).filter((issue) => issue.severity === 'error' && issue.description !== 'CoEdgeDef has no Curve2D representation').length
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
const crossKind = records.filter((record) => record.resultKind && record.resultKind !== record.sourceKind).length
report.capabilities = capabilities
report.nativeCapabilities = nativeCapabilities
report.history = { recordCount: records.length, crossKindRecords: crossKind, relations: { modified, generated, deleted } }
report.twoSidedHistory = { recordCount: twoSidedRecords.length, structuralValid: twoSidedQuality.nativeKernelValid === true, graphStructuralValid: twoSidedQuality.structuralValid, graphStructuralErrors: twoSidedQuality.structuralErrors, graphStructuralWarnings: twoSidedQuality.structuralWarnings, graphStructuralIssues: twoSidedQuality.structuralIssues, unexplainedStructuralErrors: twoSidedUnexplainedErrors, solids: twoSidedQuality.solids, volume: twoSidedMass.volume, stages: twoSidedStages.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.result = { structuralValid: quality.structuralValid, solids: quality.solids, volume: mass.volume }
await runtime.release(twoSidedResult)
await runtime.release(result)
await runtime.release(profile)
report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
await markerDirectory.removeEntry('native-revolution-history-chrome-marker.json').catch(() => {})
report.status = report.crossOriginIsolated && capabilities.status === 'ready' && nativeCapabilities.availability === 'available' && nativeCapabilities.operations.includes('revolution') && nativeCapabilities.operations.includes('rotate') && records.length > 0 && modified > 0 && generated > 0 && crossKind > 0 && quality.structuralValid && quality.solids === 1 && Math.abs(mass.volume - 24 * Math.PI) <= 1e-5 && twoSidedRecords.length > 0 && twoSidedQuality.nativeKernelValid === true && twoSidedUnexplainedErrors === 0 && twoSidedQuality.solids === 1 && twoSidedStages.length === 2 && twoSidedStages[0].operation === 'rotate' && twoSidedStages[0].inputObjectIds.join(',') === 'Profile' && twoSidedStages[0].topology.entries.length > 0 && twoSidedStages[1].operation === 'revolution' && twoSidedStages[1].inputObjectIds.join(',') === `${twoSidedStages[0].resultObjectId}` && twoSidedStages[1].resultObjectId === 'RevolutionTwoSided' && twoSidedStages[1].topology.entries.length > 0 && Math.abs(twoSidedMass.volume - 12 * Math.PI) <= 1e-5 && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? '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-revolution-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome native Revolution history harness failed.')
</script>

View File

@@ -0,0 +1,22 @@
<!doctype html><meta charset="utf-8"><title>Chrome native Thickness 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'
import { NativeOcctHistoryCoordinator } from '/src/facade/nativeHistoryProtocol.ts'
const output = document.querySelector('#result'); const report = { schemaVersion: 1, browserId: 'chrome', executionContext: 'geometry-and-history-workers', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
let runtime, provider, base, result, stage = 'init'
try {
stage = 'initialize'; 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-thickness', documentVersion: 1 }
stage = 'create-box'; base = await runtime.createBox({ ...context, width: 6, length: 6, height: 6 })
stage = 'bitbybit-thickness'; result = await runtime.thickness({ ...context, base, offset: -0.4, removeFaceIndexes: [1], joinType: 'Arc' })
stage = 'quality'; const [quality, mass] = await Promise.all([runtime.qualityReport(result), runtime.massProperties(result)])
const directory = await navigator.storage.getDirectory(); const markerName = 'native-thickness-history-chrome-marker.json'; const marker = await directory.getFileHandle(markerName, { create: true }); const writable = await marker.createWritable(); await writable.write(JSON.stringify({ operation: 'thickness', provider: nativeCapabilities.providerId })); await writable.close(); const markerText = await (await marker.getFile()).text()
stage = 'native-history'; const baseStep = await runtime.exportStep(base, 'Base.step'); const execution = await new NativeOcctHistoryCoordinator(() => 1).capture(provider, { ...context, operationId: 'chrome-native-thickness-history', operation: 'thickness', objectStep: baseStep.text, offset: -0.4, joinType: 'Arc', faceIndex: 1 }); if (execution.status !== 'completed' || !execution.response) throw new Error(`Native Thickness history ${execution.status}.`); const records = execution.response.history.records
const relations = { modified: records.filter((record) => record.relation === 'modified').length, generated: records.filter((record) => record.relation === 'generated').length, deleted: records.filter((record) => record.relation === 'deleted').length }
report.capabilities = capabilities; report.nativeCapabilities = nativeCapabilities; report.history = { recordCount: records.length, relations, sourceObjects: [...new Set(records.map((record) => record.source === 'object' ? 'Base' : 'Tool'))] }; report.result = { bitbybitStructuralValid: quality.structuralValid, bitbybitSolids: quality.solids, bitbybitVolume: mass.volume, nativeStructuralValid: execution.response.history.summary.isValid, nativeSolids: execution.response.history.summary.solids, nativeVolume: execution.response.history.summary.volume }; report.opfs = { markerCreated: true, markerSuite: JSON.parse(markerText).operation, markerRemoved: false }
await directory.removeEntry(markerName); report.opfs.markerRemoved = true; await runtime.release(result); result = undefined; await runtime.release(base); base = undefined; report.afterRelease = { shapeCount: runtime.capabilities().shapeCount, kernelReferenceCount: runtime.capabilities().kernelReferenceCount }
report.status = report.crossOriginIsolated && capabilities.status === 'ready' && nativeCapabilities.operations.includes('thickness') && records.length > 0 && relations.modified > 0 && quality.solids === 1 && mass.volume > 0 && report.result.nativeStructuralValid && report.result.nativeSolids === 1 && report.result.nativeVolume > 0 && report.opfs.markerRemoved && report.afterRelease.shapeCount === 0 && report.afterRelease.kernelReferenceCount === 0 ? 'pass' : 'failed'
} catch (error) { report.status = 'failed'; report.error = `${stage}: ${error instanceof Error ? `${error.name}: ${error.message}` : String(error)}` } finally { if (result) await runtime?.release(result).catch(() => {}); if (base) await runtime?.release(base).catch(() => {}); provider?.dispose(); runtime?.dispose() }
output.textContent = JSON.stringify(report, null, 2); await fetch('/__native-thickness-history-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) }); if (report.status !== 'pass') throw new Error(report.error || 'Chrome native Thickness history harness failed.')
</script>

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chrome Offline Harness</title></head><body><pre id="result">running</pre><script type="module" src="/src/chromeOfflineHarness.ts"></script></body></html>

View File

@@ -0,0 +1,101 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome OPFS migration harness</title>
<pre id="result">running</pre>
<script type="module">
import { createSqliteProjectPersistence } from '/src/facade/projectStore.ts'
const output = document.querySelector('#result')
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
const documentId = `chrome-rel02-${crypto.randomUUID()}`
const resourceBytes = new TextEncoder().encode('legacy-opfs-resource')
const resourceHash = [...new Uint8Array(await crypto.subtle.digest('SHA-256', resourceBytes))].map((value) => value.toString(16).padStart(2, '0')).join('')
const databasePath = `/bitbybit-rel02-${crypto.randomUUID()}.sqlite3`
const removeDatabaseFiles = async () => {
const root = await navigator.storage.getDirectory()
const prefix = databasePath.slice(1)
const removed = []
for await (const [name] of root.entries()) {
if (name !== prefix && !name.startsWith(prefix)) continue
try { await root.removeEntry(name); removed.push(name) } catch {}
}
return removed
}
const seedLegacyDatabase = () => new Promise((resolve, reject) => {
const worker = new Worker('/src/facade/projectMigrationSeedWorker.ts', { type: 'module', name: 'qa-rel02-legacy-seed' })
const timeout = setTimeout(() => { worker.terminate(); reject(new Error('Legacy OPFS seed Worker timed out.')) }, 60_000)
worker.onmessage = (event) => { clearTimeout(timeout); worker.terminate(); if (event.data?.ok) resolve(event.data); else reject(new Error(event.data?.error || 'Legacy OPFS seed failed.')) }
worker.onerror = (event) => { clearTimeout(timeout); worker.terminate(); reject(new Error(event.message || 'Legacy OPFS seed Worker failed.')) }
worker.postMessage({ operation: 'seed', databasePath, documentId, resourceHash, resourceBytes: resourceBytes.buffer }, [resourceBytes.buffer])
})
const inspectDatabase = () => new Promise((resolve, reject) => {
const worker = new Worker('/src/facade/projectMigrationSeedWorker.ts', { type: 'module', name: 'qa-rel02-legacy-inspect' })
const timeout = setTimeout(() => { worker.terminate(); reject(new Error('Legacy OPFS inspect Worker timed out.')) }, 60_000)
worker.onmessage = (event) => { clearTimeout(timeout); worker.terminate(); if (event.data?.ok) resolve(event.data); else reject(new Error(event.data?.error || 'Legacy OPFS inspect failed.')) }
worker.onerror = (event) => { clearTimeout(timeout); worker.terminate(); reject(new Error(event.message || 'Legacy OPFS inspect Worker failed.')) }
worker.postMessage({ operation: 'inspect', databasePath })
})
let persistence
let reopened
let failedPersistence
try {
if (!report.crossOriginIsolated || !navigator.storage?.getDirectory) throw new Error('Chrome REL-02 harness requires isolated OPFS.')
await removeDatabaseFiles()
await seedLegacyDatabase()
failedPersistence = createSqliteProjectPersistence({ databasePath, migrationInterruptAfterVersion: 3 })
let rollbackError = ''
try { await failedPersistence.initialize() } catch (error) { rollbackError = error instanceof Error ? error.message : String(error) }
await failedPersistence.dispose()
failedPersistence = undefined
const afterRollback = await inspectDatabase()
persistence = createSqliteProjectPersistence({ databasePath })
const migrated = await persistence.initialize()
const loaded = await persistence.load(documentId)
const restoredResource = await persistence.resource.get(resourceHash)
const recovery = await persistence.recovery(documentId)
const migratedDocument = loaded ? { ...loaded, version: loaded.version + 1, dirty: false, label: 'Migrated OPFS project' } : null
if (!migratedDocument) throw new Error('Legacy document did not load after migration.')
await persistence.save(migratedDocument)
await persistence.dispose()
persistence = undefined
reopened = createSqliteProjectPersistence({ databasePath })
const reopenedCapabilities = await reopened.initialize()
const reopenedDocument = await reopened.load(documentId)
const checkpoint = await reopened.loadCheckpoint(documentId, migratedDocument.version)
const reopenedRecovery = await reopened.recovery(documentId)
await reopened.resource.release(resourceHash)
const releasedResource = await reopened.resource.get(resourceHash)
report.migration = {
sourceSchemaVersion: 1,
migratedSchemaVersion: migrated.schemaVersion,
migratedDocumentVersion: loaded?.version ?? null,
migratedLabel: loaded?.label ?? null,
migratedObjectCount: loaded?.objects.length ?? 0,
recomputeDefaulted: loaded?.recompute?.status === 'idle',
reopenedSchemaVersion: reopenedCapabilities.schemaVersion,
reopenedVersion: reopenedDocument?.version ?? null,
reopenedLabel: reopenedDocument?.label ?? null,
checkpointVersion: checkpoint?.version ?? null,
recoveryIntegrity: recovery.integrity,
reopenedRecoveryIntegrity: reopenedRecovery.integrity,
resourceRoundTrip: restoredResource ? new TextDecoder().decode(restoredResource) === 'legacy-opfs-resource' : false,
resourceReleased: releasedResource === null,
}
report.rollback = { rejected: rollbackError.includes('interruption after schema version 3'), interruptedAfterMarker: rollbackError.includes('interruption after schema version 3'), error: rollbackError, appliedVersionsAfterFailure: afterRollback.appliedVersions || [] }
report.cleanup = { databasePath, removedFiles: [] }
report.status = report.crossOriginIsolated && migrated.mode === 'sqlite-opfs' && migrated.schemaVersion === 6 && report.rollback.rejected && report.rollback.interruptedAfterMarker && report.rollback.appliedVersionsAfterFailure.length === 1 && report.rollback.appliedVersionsAfterFailure[0] === 1 && report.migration.migratedDocumentVersion === 3 && report.migration.migratedObjectCount === 1 && report.migration.recomputeDefaulted && report.migration.reopenedSchemaVersion === 6 && report.migration.reopenedVersion === 4 && report.migration.checkpointVersion === 4 && report.migration.recoveryIntegrity === 'ok' && report.migration.reopenedRecoveryIntegrity === 'ok' && report.migration.resourceRoundTrip && report.migration.resourceReleased ? 'pass' : 'failed'
} catch (error) {
report.status = 'failed'
report.error = error instanceof Error ? `${error.name}: ${error.message}` : String(error)
} finally {
await persistence?.dispose().catch(() => {})
await reopened?.dispose().catch(() => {})
await failedPersistence?.dispose().catch(() => {})
try { report.cleanup = { databasePath, removedFiles: await removeDatabaseFiles() } } catch (error) { report.cleanupError = error instanceof Error ? error.message : String(error) }
}
output.textContent = JSON.stringify(report, null, 2)
await fetch('/__opfs-migration-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome OPFS migration harness failed.')
</script>

View File

@@ -0,0 +1,128 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome PartDesign lifecycle harness</title>
<pre id="result">running</pre>
<script type="module">
import { createMockFacade } from '/src/facade/mockFacade.ts'
const output = document.querySelector('#result')
const report = {
schemaVersion: 1,
browserId: 'chrome',
crossOriginIsolated: globalThis.crossOriginIsolated === true,
status: 'running',
}
const facade = createMockFacade()
const property = (objectId, name) => facade.app.document.getObject(objectId)?.properties.find((entry) => entry.name === name)?.value ?? null
const shapeEvidence = async (objectId) => {
const shape = facade.geometry.getObjectShape(objectId)
if (!shape) throw new Error(`${objectId} did not expose a ShapeHandle.`)
const [mass, quality] = await Promise.all([facade.geometry.massProperties(shape), facade.geometry.qualityReport(shape)])
if (!(mass.volume > 0) || quality.solids !== 1) throw new Error(`${objectId} did not expose one positive-volume solid.`)
return { shapeId: shape.id, volume: mass.volume, solids: quality.solids, structuralValid: quality.structuralValid }
}
const rectangle = [
{ id: 'lifecycle-edge-1', type: 'line', start: { x: 0, y: 0 }, end: { x: 4, y: 0 } },
{ id: 'lifecycle-edge-2', type: 'line', start: { x: 4, y: 0 }, end: { x: 4, y: 3 } },
{ id: 'lifecycle-edge-3', type: 'line', start: { x: 4, y: 3 }, end: { x: 0, y: 3 } },
{ id: 'lifecycle-edge-4', type: 'line', start: { x: 0, y: 3 }, end: { x: 0, y: 0 } },
]
try {
if (!report.crossOriginIsolated || !navigator.storage?.getDirectory) throw new Error('Chrome PartDesign lifecycle requires isolated OPFS.')
const initialized = await facade.geometry.initialize()
if (initialized.status !== 'ready') throw new Error(initialized.reason || 'Bitbybit geometry runtime is unavailable.')
for (const edge of rectangle) facade.app.sketcher.addGeometry('sketch', edge)
if (facade.gui.command.getState('pad').status !== 'enabled') throw new Error('Closed Sketch did not enable Pad.')
const initialOrder = facade.app.document.getActive().tree.find((item) => item.id === 'body')?.children ?? []
let reorderRejected = false
try { facade.app.document.reorderBodyFeature({ bodyId: 'body', objectId: 'pocket', beforeObjectId: 'pad' }) } catch (error) { reorderRejected = /dependency/.test(String(error)) }
let dependencyRemovalRejected = false
try { facade.app.document.removeObject({ objectId: 'pad' }) } catch (error) { dependencyRemovalRejected = /dependent/.test(String(error)) }
const removed = facade.app.document.removeObject({ objectId: 'fillet' })
const deleted = { removed, tip: property('body', 'Tip'), filletExists: facade.app.document.getObject('fillet') !== null }
facade.history.undo()
const deletionUndone = { tip: property('body', 'Tip'), filletExists: facade.app.document.getObject('fillet') !== null }
facade.history.redo()
const deletionRedone = { tip: property('body', 'Tip'), filletExists: facade.app.document.getObject('fillet') !== null }
facade.history.undo()
const restoredOrder = facade.app.document.getActive().tree.find((item) => item.id === 'body')?.children ?? []
const structural = { initialOrder, restoredOrder, reorderRejected, dependencyRemovalRejected, deleted, deletionUndone, deletionRedone, restoredTip: property('body', 'Tip') }
if (!reorderRejected || !dependencyRemovalRejected || deleted.tip !== 'pocket' || deleted.filletExists || !deletionUndone.filletExists || deletionUndone.tip !== 'fillet' || deletionRedone.filletExists || deletionRedone.tip !== 'pocket' || structural.restoredTip !== 'fillet') throw new Error('PartDesign structural lifecycle is inconsistent.')
facade.app.document.setProperty({ objectId: 'pocket', propertyName: 'Suppressed', value: true })
facade.app.document.setProperty({ objectId: 'fillet', propertyName: 'Suppressed', value: true })
const suppression = await facade.app.document.recomputeAsync()
if (suppression.status !== 'completed') throw new Error(`Suppression recompute failed: ${suppression.status}.`)
const initialShape = await shapeEvidence('pad')
const initial = {
length: property('pad', 'Length'),
tip: property('body', 'Tip'),
support: property('sketch', 'Support'),
pocketSuppressed: property('pocket', 'Suppressed'),
filletSuppressed: property('fillet', 'Suppressed'),
...initialShape,
}
facade.app.document.setProperty({ objectId: 'pad', propertyName: 'Length', value: 31 })
const editedRecompute = await facade.app.document.recomputeAsync()
const editedShape = await shapeEvidence('pad')
const edited = {
length: property('pad', 'Length'),
tip: property('body', 'Tip'),
support: property('sketch', 'Support'),
recompute: editedRecompute.status,
shapeCount: facade.geometry.capabilities().shapeCount,
kernelReferenceCount: facade.geometry.capabilities().kernelReferenceCount,
...editedShape,
}
facade.history.undo()
const undoRecompute = await facade.app.document.recomputeAsync({ dirtyObjectIds: ['pad'] })
const undoneShape = await shapeEvidence('pad')
const undone = {
length: property('pad', 'Length'),
tip: property('body', 'Tip'),
support: property('sketch', 'Support'),
recompute: undoRecompute.status,
shapeCount: facade.geometry.capabilities().shapeCount,
kernelReferenceCount: facade.geometry.capabilities().kernelReferenceCount,
...undoneShape,
}
const saved = await facade.project.save()
const persisted = await facade.project.load('doc-pump-housing')
await facade.app.document.load('doc-pump-housing')
const reopenedRecompute = await facade.app.document.recomputeAsync({ dirtyObjectIds: ['pad'] })
const reopenedShape = await shapeEvidence('pad')
const reopened = {
length: property('pad', 'Length'),
tip: property('body', 'Tip'),
support: property('sketch', 'Support'),
pocketSuppressed: property('pocket', 'Suppressed'),
filletSuppressed: property('fillet', 'Suppressed'),
persistedLength: persisted?.objects.find((object) => object.id === 'pad')?.properties.find((entry) => entry.name === 'Length')?.value ?? null,
savedVersion: saved.documentVersion,
activeVersion: facade.app.document.getActive().version,
recompute: reopenedRecompute.status,
persistenceMode: facade.project.capabilities().mode,
...reopenedShape,
}
facade.geometry.dispose()
await new Promise((resolve) => setTimeout(resolve, 0))
const released = {
shapeCount: facade.geometry.capabilities().shapeCount,
kernelReferenceCount: facade.geometry.capabilities().kernelReferenceCount,
}
Object.assign(report, { status: 'pass', structural, initial, edited, undone, reopened, released })
} catch (error) {
facade.geometry.dispose()
Object.assign(report, { status: 'failed', error: error instanceof Error ? error.stack || error.message : String(error) })
}
output.textContent = JSON.stringify(report, null, 2)
await fetch('/__partdesign-lifecycle-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
</script>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome PartDesign loft harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromePartdesignLoftHarness.ts"></script>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome PartDesign transform harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromePartdesignTransformHarness.ts"></script>

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chrome Performance Harness</title></head><body><pre id="result">running</pre><script type="module" src="/src/chromePerformanceHarness.ts"></script></body></html>

View File

@@ -0,0 +1,40 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome OPFS persistence harness</title>
<pre id="result">running</pre>
<script type="module">
import { createSqliteProjectPersistence } from '/src/facade/projectStore.ts'
const output = document.querySelector('#result')
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
const documentSnapshot = {
id: 'chrome-opfs-persistence', label: 'Chrome OPFS persistence', version: 7, dirty: true, readOnly: false, units: 'mm',
tree: [{ id: 'root', label: 'Root', type: 'feature', state: 'valid' }],
objects: [{ id: 'root', typeId: 'PartDesign::Feature', properties: [{ name: 'Label', label: 'Label', type: 'App::PropertyString', value: 'Root', recompute: false }] }],
dependencies: [], recompute: { generation: 2, status: 'completed', objectStates: { root: 'up-to-date' }, dirtyObjects: [], order: ['root'], errors: [] },
}
let persistence
try {
persistence = createSqliteProjectPersistence()
const capabilities = await persistence.initialize()
const saved = await persistence.save(documentSnapshot)
const loaded = await persistence.load(documentSnapshot.id)
const checkpoint = await persistence.loadCheckpoint(documentSnapshot.id, documentSnapshot.version)
const bytes = new TextEncoder().encode('chrome-opfs-resource')
const resource = await persistence.resource.put(bytes, 'text/plain')
const restoredBytes = await persistence.resource.get(resource.hash)
await persistence.resource.release(resource.hash)
const recovery = await persistence.recovery(documentSnapshot.id)
report.capabilities = capabilities
report.roundTrip = { savedMode: saved.mode, loadedVersion: loaded?.version ?? null, loadedLabel: loaded?.label ?? null, checkpointVersion: checkpoint?.version ?? null, resourceRoundTrip: restoredBytes ? new TextDecoder().decode(restoredBytes) === 'chrome-opfs-resource' : false, recoveryIntegrity: recovery.integrity, checkpointCount: recovery.checkpoints.length }
report.status = report.crossOriginIsolated && capabilities.mode === 'sqlite-opfs' && capabilities.sqliteWasm && capabilities.opfs && saved.mode === 'sqlite-opfs' && loaded?.id === documentSnapshot.id && loaded.version === documentSnapshot.version && checkpoint?.version === documentSnapshot.version && report.roundTrip.resourceRoundTrip && recovery.integrity === 'ok' ? 'pass' : 'failed'
} catch (error) {
report.status = 'failed'
report.error = error instanceof Error ? `${error.name}: ${error.message}` : String(error)
} finally {
await persistence?.dispose().catch(() => {})
}
output.textContent = JSON.stringify(report, null, 2)
await fetch('/__persistence-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome OPFS persistence harness failed.')
</script>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Plot harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromePlotHarness.ts"></script>

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chrome Production Document Harness</title></head><body><pre id="result">running</pre><script type="module" src="/src/chromeProductionDocumentHarness.ts"></script></body></html>

View File

@@ -0,0 +1,112 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Sketcher profile validation harness</title>
<pre id="result">running</pre>
<script type="module">
import { classifyPlanarProfile, validatePlanarProfile } from '/src/facade/geometryRuntime.ts'
import { BitbybitGeometryRuntime } from '/src/facade/geometryRuntime.ts'
import { sketchProfile } from '/src/facade/recomputeEngine.ts'
import { createSketch } from '/src/facade/sketcher.ts'
const output = document.querySelector('#result')
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
const outer = [[0, 0, 0], [6, 0, 0], [6, 6, 0], [0, 6, 0]]
const hole = [[2, 2, 0], [2, 4, 0], [4, 4, 0], [4, 2, 0]]
const bowTie = { outer: [[0, 0, 0], [4, 4, 0], [0, 4, 0], [4, 0, 0]] }
const outsideHole = { outer, holes: [[[8, 8, 0], [8, 9, 0], [9, 9, 0], [9, 8, 0]]] }
const nonPlanar = { outer: [[0, 0, 0], [2, 0, 0], [2, 2, 1], [0, 2, 0]] }
const degenerate = { outer: [[0, 0, 0], [1, 0, 0], [2, 0, 0]] }
const classification = (profile) => classifyPlanarProfile(profile)
const profileSketch = (id, loops) => createSketch(id, loops.flatMap((loop, loopIndex) => loop.map((start, pointIndex) => {
const end = loop[(pointIndex + 1) % loop.length]
return { id: `${loopIndex}-${pointIndex}`, type: 'line', start: { x: start[0], y: start[1] }, end: { x: end[0], y: end[1] } }
})))
const validationError = (profile) => {
try {
validatePlanarProfile(profile)
return null
} catch (error) {
return error instanceof Error ? error.message : String(error)
}
}
try {
const markerDirectory = await navigator.storage.getDirectory()
const markerName = 'profile-validation-chrome-marker.json'
const marker = await markerDirectory.getFileHandle(markerName, { create: true })
const writable = await marker.createWritable()
await writable.write(JSON.stringify({ suite: 'SK-11', cases: 12 }))
await writable.close()
const markerPayload = JSON.parse(await (await marker.getFile()).text())
await markerDirectory.removeEntry(markerName)
let markerRemoved = false
try { await markerDirectory.getFileHandle(markerName) } catch (error) { markerRemoved = error?.name === 'NotFoundError' }
report.fixtures = {
closed: classification({ outer }),
open: classification({ outer, closed: false }),
multiRing: classification({ outer, holes: [hole] }),
selfIntersecting: classification(bowTie),
invalidNesting: classification(outsideHole),
nonPlanar: classification(nonPlanar),
degenerate: classification(degenerate),
}
report.validationErrors = {
closed: validationError({ outer }),
multiRing: validationError({ outer, holes: [hole] }),
open: validationError({ outer, closed: false }),
selfIntersecting: validationError(bowTie),
invalidNesting: validationError(outsideHole),
nonPlanar: validationError(nonPlanar),
degenerate: validationError(degenerate),
}
const partDesignClosed = sketchProfile(profileSketch('partdesign-closed', [outer.map(([x, y]) => [x, y])]))
const partDesignMultiRing = sketchProfile(profileSketch('partdesign-multi-ring', [hole.map(([x, y]) => [x, y]), outer.map(([x, y]) => [x, y])]))
const partDesignSelfIntersecting = sketchProfile(profileSketch('partdesign-self-intersecting', [[[0, 0], [4, 4], [0, 4], [4, 0]]]))
const partDesignInvalidNesting = sketchProfile(profileSketch('partdesign-invalid-nesting', [outer.map(([x, y]) => [x, y]), [[8, 8], [9, 8], [9, 9], [8, 9]]]))
const partDesignOpen = sketchProfile(createSketch('partdesign-open', [
{ id: 'open-0', type: 'line', start: { x: 0, y: 0 }, end: { x: 6, y: 0 } },
{ id: 'open-1', type: 'line', start: { x: 6, y: 0 }, end: { x: 6, y: 6 } },
{ id: 'open-2', type: 'line', start: { x: 6, y: 6 }, end: { x: 0, y: 6 } },
]))
report.partDesignProfiles = {
closed: { code: partDesignClosed.code ?? null, regionCount: 1 + (partDesignClosed.profile?.additionalRegions?.length ?? 0) },
multiRing: { code: partDesignMultiRing.code ?? null, outerPoints: partDesignMultiRing.profile?.outer.length ?? 0, holeCount: partDesignMultiRing.profile?.holes?.length ?? 0, regionCount: 1 + (partDesignMultiRing.profile?.additionalRegions?.length ?? 0) },
open: { code: partDesignOpen.code ?? null },
selfIntersecting: { code: partDesignSelfIntersecting.code ?? null, regionCount: 1 + (partDesignSelfIntersecting.profile?.additionalRegions?.length ?? 0), regionPoints: [partDesignSelfIntersecting.profile?.outer.length ?? 0, ...(partDesignSelfIntersecting.profile?.additionalRegions ?? []).map((region) => region.outer.length)] },
invalidNesting: { code: partDesignInvalidNesting.code ?? null, regionCount: 1 + (partDesignInvalidNesting.profile?.additionalRegions?.length ?? 0), regionPoints: [partDesignInvalidNesting.profile?.outer.length ?? 0, ...(partDesignInvalidNesting.profile?.additionalRegions ?? []).map((region) => region.outer.length)] },
}
const runtime = new BitbybitGeometryRuntime()
const geometryProfiles = { closed: partDesignClosed.profile, multiRing: partDesignMultiRing.profile, selfIntersecting: partDesignSelfIntersecting.profile, invalidNesting: partDesignInvalidNesting.profile }
const expectedGeometry = { closed: [360, 1, 'solid'], multiRing: [320, 1, 'solid'], selfIntersecting: [80, 2, 'compound'], invalidNesting: [370, 2, 'compound'] }
const geometryShapes = []
report.partDesignGeometry = {}
try {
const capabilities = await runtime.initialize()
if (capabilities.status !== 'ready') throw new Error(capabilities.reason || `Bitbybit geometry status is ${capabilities.status}`)
for (const [name, profile] of Object.entries(geometryProfiles)) {
if (!profile) throw new Error(`PartDesign ${name} profile was not produced.`)
const shape = await runtime.pad({ documentId: 'sk-11-profile-oracle', documentVersion: 1, profile, length: 10, direction: [0, 0, 1] })
geometryShapes.push(shape)
const mass = await runtime.massProperties(shape)
const quality = await runtime.qualityReport(shape)
report.partDesignGeometry[name] = { volume: mass.volume, solids: quality.solids, shapeType: quality.shapeType, structuralValid: quality.structuralValid, structuralErrors: quality.structuralErrors, structuralWarnings: quality.structuralWarnings }
}
} finally {
await Promise.all(geometryShapes.map((shape) => runtime.release(shape)))
runtime.dispose()
}
report.opfs = { markerSuite: markerPayload.suite, markerCases: markerPayload.cases, markerRemoved }
const is = (fixture, status, ringCount, intersections = 0) => fixture?.status === status && fixture.ringCount === ringCount && fixture.selfIntersections === intersections
const validErrors = report.validationErrors.closed === null && report.validationErrors.multiRing === null
const expectedErrors = /open/.test(report.validationErrors.open ?? '') && /self-intersecting/.test(report.validationErrors.selfIntersecting ?? '') && /contained directly/.test(report.validationErrors.invalidNesting ?? '') && /coplanar/.test(report.validationErrors.nonPlanar ?? '') && /collinear/.test(report.validationErrors.degenerate ?? '')
const partDesignValid = report.partDesignProfiles.closed.code === null && report.partDesignProfiles.closed.regionCount === 1 && report.partDesignProfiles.multiRing.code === null && report.partDesignProfiles.multiRing.outerPoints === 4 && report.partDesignProfiles.multiRing.holeCount === 1 && report.partDesignProfiles.multiRing.regionCount === 1 && report.partDesignProfiles.open.code === 'PROFILE_OPEN' && report.partDesignProfiles.selfIntersecting.code === null && report.partDesignProfiles.selfIntersecting.regionCount === 2 && report.partDesignProfiles.selfIntersecting.regionPoints.every((count) => count === 3) && report.partDesignProfiles.invalidNesting.code === null && report.partDesignProfiles.invalidNesting.regionCount === 2 && report.partDesignProfiles.invalidNesting.regionPoints.every((count) => count === 4)
const geometryValid = Object.entries(expectedGeometry).every(([name, [volume, solids, shapeType]]) => Math.abs(report.partDesignGeometry[name]?.volume - volume) <= 1e-7 && report.partDesignGeometry[name]?.solids === solids && report.partDesignGeometry[name]?.shapeType === shapeType)
report.status = report.crossOriginIsolated && is(report.fixtures.closed, 'closed', 1) && is(report.fixtures.open, 'open', 1) && is(report.fixtures.multiRing, 'multi-ring', 2) && is(report.fixtures.selfIntersecting, 'self-intersecting', 1, 1) && is(report.fixtures.invalidNesting, 'invalid-nesting', 2) && is(report.fixtures.nonPlanar, 'non-planar', 1) && is(report.fixtures.degenerate, 'degenerate', 1) && validErrors && expectedErrors && partDesignValid && geometryValid && report.opfs.markerSuite === 'SK-11' && report.opfs.markerCases === 12 && report.opfs.markerRemoved ? 'pass' : 'failed'
} catch (error) {
report.status = 'failed'
report.error = error instanceof Error ? error.message : String(error)
}
output.textContent = JSON.stringify(report, null, 2)
await fetch('/__chrome-profile-validation-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome profile validation harness failed.')
</script>

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chrome QA-08 Harness</title></head><body><pre id="result">running</pre><script type="module" src="/src/chromeQa08Harness.ts"></script></body></html>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Robot harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeRobotHarness.ts"></script>

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chrome Script Harness</title></head><body><pre id="result">running</pre><script type="module" src="/src/chromeScriptHarness.ts"></script></body></html>

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chrome Secondary Formats Harness</title></head><body><pre id="result">running</pre><script type="module" src="/src/chromeSecondaryFormatsHarness.ts"></script></body></html>

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chrome Security Harness</title></head><body><pre id="result">running</pre><script type="module" src="/src/chromeSecurityHarness.ts"></script></body></html>

View File

@@ -0,0 +1,89 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Sketcher B-spline editor harness</title>
<pre id="result">running</pre>
<script type="module">
import { createSketch, editSketchBspline, sketchGeometrySignature } from '/src/facade/sketcher.ts'
import { inspectFcstdArchive, serializeFcstdMetadataArchive } from '/src/facade/fcstd.ts'
const output = document.querySelector('#result')
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
const source = createSketch('chrome-bspline', [{ id: 'spline', type: 'bspline', degree: 2, controlPoints: [{ x: 0, y: 0 }, { x: 1, y: 2 }, { x: 3, y: 0 }], weights: [1, 1, 1], knots: [0, 0, 0, 1, 1, 1] }])
const errorFor = (patch) => {
try {
editSketchBspline(source, 'spline', patch)
return null
} catch (error) {
return error instanceof Error ? error.message : String(error)
}
}
try {
const edited = editSketchBspline(source, 'spline', {
degree: 1,
controlPoints: [{ x: 0, y: 0 }, { x: 2, y: 3 }, { x: 4, y: 0 }],
weights: [1, 0.5, 1],
knots: [0, 0, 0.5, 1, 1],
periodic: true,
})
const editedGeometry = edited.geometry[0]
const sourceGeometry = source.geometry[0]
const constrained = createSketch('Sketch', [{
id: 'weighted-spline', type: 'bspline', degree: 3,
controlPoints: [{ x: 0, y: 0 }, { x: 1, y: 2 }, { x: 3, y: 2 }, { x: 4, y: 0 }],
weights: [1, 1.5, 1.25, 1], knots: [0, 0, 0, 0, 1, 1, 1, 1], periodic: false,
}], [
{ id: 'middle-weight', type: 'weight', geometryId: 'weighted-spline', controlPointIndex: 1, value: 1.5, driving: true },
{ id: 'third-alignment', type: 'internalAlignment', geometryId: 'weighted-spline', internalGeometryIndex: 2, alignmentType: 'bspline-control-point', driving: true },
{ id: 'third-weight', type: 'weight', geometryId: 'weighted-spline', controlPointIndex: 2, value: 1.25, driving: true },
])
const document = {
id: 'chrome-bspline-fcstd', label: 'B-spline round-trip', version: 1, dirty: true, readOnly: false, units: 'mm',
tree: [{ id: 'Sketch', label: 'Sketch', type: 'sketch', state: 'valid' }],
objects: [{ id: 'Sketch', typeId: 'Sketcher::SketchObject', properties: [], sketch: constrained }],
dependencies: [], recompute: { generation: 0, status: 'idle', objectStates: { Sketch: 'up-to-date' }, dirtyObjects: [], order: [], errors: [] },
}
const archive = serializeFcstdMetadataArchive(document)
const restored = inspectFcstdArchive(archive).objects[0]?.sketch
const markerDirectory = await navigator.storage.getDirectory()
const markerName = 'sketcher-bspline-chrome-marker.json'
const marker = await markerDirectory.getFileHandle(markerName, { create: true })
const writable = await marker.createWritable()
await writable.write(JSON.stringify({ suite: 'SK-10', fields: 8 }))
await writable.close()
const markerPayload = JSON.parse(await (await marker.getFile()).text())
await markerDirectory.removeEntry(markerName)
let markerRemoved = false
try { await markerDirectory.getFileHandle(markerName) } catch (error) { markerRemoved = error?.name === 'NotFoundError' }
report.edit = {
stableId: editedGeometry?.id === 'spline',
degree: editedGeometry?.type === 'bspline' ? editedGeometry.degree : null,
poleCount: editedGeometry?.type === 'bspline' ? editedGeometry.controlPoints.length : 0,
middlePole: editedGeometry?.type === 'bspline' ? editedGeometry.controlPoints[1] : null,
weights: editedGeometry?.type === 'bspline' ? editedGeometry.weights : null,
knots: editedGeometry?.type === 'bspline' ? editedGeometry.knots : null,
periodic: editedGeometry?.type === 'bspline' ? editedGeometry.periodic === true : false,
sourceUnchanged: sketchGeometrySignature(sourceGeometry) === JSON.stringify({ id: 'spline', type: 'bspline', degree: 2, controlPoints: [{ x: 0, y: 0 }, { x: 1, y: 2 }, { x: 3, y: 0 }], weights: [1, 1, 1], knots: [0, 0, 0, 1, 1, 1] }),
}
report.validationErrors = {
invalidWeight: errorFor({ weights: [1, 0, 1] }),
invalidKnots: errorFor({ knots: [0, 1] }),
invalidDegree: errorFor({ degree: 3 }),
}
report.roundTrip = {
archiveBytes: archive.byteLength,
geometryEqual: JSON.stringify(restored?.geometry) === JSON.stringify(constrained.geometry),
constraintsEqual: JSON.stringify(restored?.constraints) === JSON.stringify(constrained.constraints),
constraintTypes: restored?.constraints.map((constraint) => constraint.type) ?? [],
weights: restored?.geometry[0]?.type === 'bspline' ? restored.geometry[0].weights : null,
knots: restored?.geometry[0]?.type === 'bspline' ? restored.geometry[0].knots : null,
}
report.opfs = { markerSuite: markerPayload.suite, markerFields: markerPayload.fields, markerRemoved }
report.status = report.crossOriginIsolated && report.edit.stableId && report.edit.degree === 1 && report.edit.poleCount === 3 && report.edit.middlePole?.x === 2 && report.edit.middlePole?.y === 3 && JSON.stringify(report.edit.weights) === JSON.stringify([1, 0.5, 1]) && JSON.stringify(report.edit.knots) === JSON.stringify([0, 0, 0.5, 1, 1]) && report.edit.periodic && report.edit.sourceUnchanged && /weights/.test(report.validationErrors.invalidWeight ?? '') && /knots/.test(report.validationErrors.invalidKnots ?? '') && /degree \+ 1/.test(report.validationErrors.invalidDegree ?? '') && report.roundTrip.archiveBytes > 0 && report.roundTrip.geometryEqual && report.roundTrip.constraintsEqual && JSON.stringify(report.roundTrip.constraintTypes) === JSON.stringify(['weight', 'internalAlignment', 'weight']) && JSON.stringify(report.roundTrip.weights) === JSON.stringify([1, 1.5, 1.25, 1]) && JSON.stringify(report.roundTrip.knots) === JSON.stringify([0, 0, 0, 0, 1, 1, 1, 1]) && report.opfs.markerSuite === 'SK-10' && report.opfs.markerFields === 8 && report.opfs.markerRemoved ? 'pass' : 'failed'
} catch (error) {
report.status = 'failed'
report.error = error instanceof Error ? error.message : String(error)
}
output.textContent = JSON.stringify(report, null, 2)
await fetch('/__chrome-sketcher-bspline-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome Sketcher B-spline harness failed.')
</script>

View File

@@ -0,0 +1,59 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Sketcher dimensional diagnostics harness</title>
<pre id="result">running</pre>
<script type="module">
import { createSketch, solveSketch } from '/src/facade/sketcher.ts'
const output = document.querySelector('#result')
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
try {
const referenceInput = createSketch('chrome-reference', [{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 2, y: 1 } }], [
{ id: 'horizontal', type: 'horizontal', geometryId: 'line' },
{ id: 'reference-length', type: 'distance', first: { geometryId: 'line', point: 'start' }, second: { geometryId: 'line', point: 'end' }, value: 99, driving: false },
])
const duplicateInput = createSketch('chrome-duplicate', [{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 1, y: 0 } }], [
{ id: 'length-1', type: 'distance', first: { geometryId: 'line', point: 'start' }, second: { geometryId: 'line', point: 'end' }, value: 5 },
{ id: 'length-2', type: 'distance', first: { geometryId: 'line', point: 'start' }, second: { geometryId: 'line', point: 'end' }, value: 5 },
])
const conflictInput = createSketch('chrome-conflict', [{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 1, y: 0 } }], [
{ id: 'length-1', type: 'distance', first: { geometryId: 'line', point: 'start' }, second: { geometryId: 'line', point: 'end' }, value: 5 },
{ id: 'length-2', type: 'distance', first: { geometryId: 'line', point: 'start' }, second: { geometryId: 'line', point: 'end' }, value: 8 },
])
const coincidentInput = createSketch('chrome-coincident', [
{ id: 'first', type: 'line', start: { x: 0, y: 0 }, end: { x: 2, y: 0 } },
{ id: 'second', type: 'line', start: { x: 5, y: 3 }, end: { x: 6, y: 4 } },
], [{ id: 'coincident', type: 'coincident', first: { geometryId: 'first', point: 'end' }, second: { geometryId: 'second', point: 'start' } }])
const blockInput = createSketch('chrome-block', [{ id: 'line', type: 'line', start: { x: 1, y: 2 }, end: { x: 4, y: 6 } }], [{ id: 'block', type: 'block', geometryId: 'line' }])
const reference = solveSketch(referenceInput)
const duplicate = solveSketch(duplicateInput)
const conflict = solveSketch(conflictInput)
const coincident = solveSketch(coincidentInput)
const block = solveSketch(blockInput)
const markerDirectory = await navigator.storage.getDirectory()
const markerName = 'sketcher-diagnostics-chrome-marker.json'
const marker = await markerDirectory.getFileHandle(markerName, { create: true })
const writable = await marker.createWritable()
await writable.write(JSON.stringify({ suite: 'SK-06/SK-07', cases: 5 }))
await writable.close()
const markerPayload = JSON.parse(await (await marker.getFile()).text())
await markerDirectory.removeEntry(markerName)
let markerRemoved = false
try { await markerDirectory.getFileHandle(markerName) } catch (error) { markerRemoved = error?.name === 'NotFoundError' }
report.fixtures = {
reference: { status: reference.status, degreesOfFreedom: reference.degreesOfFreedom, diagnostic: reference.diagnostics.some((diagnostic) => diagnostic.code === 'REFERENCE_DIMENSION' && diagnostic.constraintId === 'reference-length'), unchangedEnd: reference.snapshot.geometry[0].type === 'line' && reference.snapshot.geometry[0].end.x === 2 },
redundant: { status: duplicate.status, diagnostic: duplicate.diagnostics.some((diagnostic) => diagnostic.code === 'REDUNDANT_CONSTRAINT' && diagnostic.constraintId === 'length-2') },
conflicting: { status: conflict.status, diagnostic: conflict.diagnostics.some((diagnostic) => diagnostic.code === 'CONSTRAINT_CONFLICT' && diagnostic.constraintId === 'length-2'), notConverged: conflict.diagnostics.some((diagnostic) => diagnostic.code === 'SOLVER_NOT_CONVERGED') },
coincident: { status: coincident.status, residual: coincident.residual, sharedPoint: coincident.snapshot.geometry[0].type === 'line' && coincident.snapshot.geometry[1].type === 'line' && coincident.snapshot.geometry[0].end.x === coincident.snapshot.geometry[1].start.x && coincident.snapshot.geometry[0].end.y === coincident.snapshot.geometry[1].start.y },
block: { status: block.status, degreesOfFreedom: block.degreesOfFreedom, unchanged: block.snapshot.geometry[0].type === 'line' && block.snapshot.geometry[0].start.x === 1 && block.snapshot.geometry[0].start.y === 2 && block.snapshot.geometry[0].end.x === 4 && block.snapshot.geometry[0].end.y === 6 },
}
report.opfs = { markerSuite: markerPayload.suite, markerRemoved }
report.status = report.crossOriginIsolated && report.fixtures.reference.status === 'under-constrained' && report.fixtures.reference.degreesOfFreedom === 3 && report.fixtures.reference.diagnostic && report.fixtures.reference.unchangedEnd && report.fixtures.redundant.status === 'under-constrained' && report.fixtures.redundant.diagnostic && report.fixtures.conflicting.status === 'conflicting' && report.fixtures.conflicting.diagnostic && report.fixtures.conflicting.notConverged && report.fixtures.coincident.status === 'under-constrained' && report.fixtures.coincident.residual === 0 && report.fixtures.coincident.sharedPoint && report.fixtures.block.status === 'solved' && report.fixtures.block.degreesOfFreedom === 0 && report.fixtures.block.unchanged && report.opfs.markerSuite === 'SK-06/SK-07' && report.opfs.markerRemoved ? 'pass' : 'failed'
} catch (error) {
report.status = 'failed'
report.error = error instanceof Error ? error.message : String(error)
}
output.textContent = JSON.stringify(report, null, 2)
await fetch('/__chrome-sketcher-diagnostics-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome Sketcher diagnostics harness failed.')
</script>

View File

@@ -0,0 +1,92 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Sketcher interactive editor harness</title>
<div id="viewport" tabindex="0" style="position:relative;width:200px;height:100px">
<button id="drag-handle" data-geometry-id="line" data-point="end" aria-label="Line end point" style="position:absolute;left:50px;top:10px;width:16px;height:16px"></button>
</div>
<pre id="result">running</pre>
<script type="module">
import { applySketchAutoConstraints, createSketch, dragSketchPoint, extendSketchLine, replaySketchEditorEvents, setSketchConstruction, SketchEditorInteractionSession, splitSketchLine, suggestSketchAutoConstraints, trimSketchLine } from '/src/facade/sketcher.ts'
const output = document.querySelector('#result')
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
const base = createSketch('chrome-editor', [
{ id: 'line-a', type: 'line', start: { x: 0, y: 0 }, end: { x: 5, y: 0.0001 } },
{ id: 'line-b', type: 'line', start: { x: 5.0001, y: 0 }, end: { x: 5, y: 4 } },
])
const line = createSketch('chrome-editor-line', [{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 10, y: 0 } }])
const dragLine = createSketch('chrome-editor-drag-line', [{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 5, y: 1 } }], [{ id: 'horizontal', type: 'horizontal', geometryId: 'line' }])
const trimLine = createSketch('chrome-editor-trim-line', [
{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 10, y: 0 } },
{ id: 'cutter', type: 'line', start: { x: 7, y: -2 }, end: { x: 7, y: 2 } },
])
const endpoint = (snapshot, id = 'line') => {
const geometry = snapshot.geometry.find((candidate) => candidate.id === id)
return geometry?.type === 'line' ? geometry : null
}
try {
const dragged = dragSketchPoint(dragLine, { geometryId: 'line', point: 'end' }, { x: 8, y: 2 })
const split = splitSketchLine(line, 'line', { x: 4, y: 0 }, 'line-2')
const extended = extendSketchLine(line, 'line', 'end', { x: 15, y: 0 })
const trimmed = trimSketchLine(trimLine, 'line', 'end', { x: 7, y: 0 })
const construction = setSketchConstruction(line, 'line', true)
const suggestions = suggestSketchAutoConstraints(base, 'line-a', 0.001)
const auto = applySketchAutoConstraints(base, suggestions)
const replay = replaySketchEditorEvents(line, [
{ type: 'drag', point: { geometryId: 'line', point: 'end' }, target: { x: 8, y: 0 } },
{ type: 'undo' },
{ type: 'redo' },
{ type: 'trim', geometryId: 'line', endpoint: 'end', target: { x: 6, y: 0 } },
])
const interaction = new SketchEditorInteractionSession(dragLine)
interaction.setTool('drag')
const viewport = document.querySelector('#viewport')
const dragHandle = document.querySelector('#drag-handle')
const nativeEvents = { pointer: 0, keyboard: 0 }
const pointerPosition = (event) => ({ x: event.clientX / 10, y: event.clientY / 10 })
viewport.addEventListener('pointerdown', (event) => {
nativeEvents.pointer += 1
const target = event.target.closest?.('[data-geometry-id]')
interaction.pointerDown({ position: pointerPosition(event), target: target ? { geometryId: target.dataset.geometryId, point: target.dataset.point } : undefined })
})
viewport.addEventListener('pointerup', (event) => { nativeEvents.pointer += 1; interaction.pointerUp({ position: pointerPosition(event) }) })
viewport.addEventListener('keydown', (event) => { nativeEvents.keyboard += 1; if (interaction.keyDown(event)) event.preventDefault() })
dragHandle.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: 50, clientY: 10, pointerId: 1 }))
viewport.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, clientX: 80, clientY: 20, pointerId: 1 }))
viewport.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'z', ctrlKey: true }))
viewport.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'y', ctrlKey: true }))
interaction.setTool('drag')
dragHandle.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: 80, clientY: 20, pointerId: 2 }))
viewport.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'Escape' }))
const markerDirectory = await navigator.storage.getDirectory()
const markerName = 'sketcher-editor-chrome-marker.json'
const marker = await markerDirectory.getFileHandle(markerName, { create: true })
const writable = await marker.createWritable()
await writable.write(JSON.stringify({ suite: 'SK-09', operations: 9 }))
await writable.close()
const markerPayload = JSON.parse(await (await marker.getFile()).text())
await markerDirectory.removeEntry(markerName)
let markerRemoved = false
try { await markerDirectory.getFileHandle(markerName) } catch (error) { markerRemoved = error?.name === 'NotFoundError' }
report.operations = {
drag: { start: endpoint(dragged.snapshot)?.start ?? null, end: endpoint(dragged.snapshot)?.end ?? null, sourceUnchanged: endpoint(dragLine)?.end?.x === 5 && endpoint(dragLine)?.end?.y === 1 },
split: { ids: split.geometry.map((geometry) => geometry.id), endpoints: split.geometry.filter((geometry) => geometry.type === 'line').map((geometry) => [geometry.start, geometry.end]), constraintTypes: split.constraints.map((constraint) => constraint.type) },
extend: { end: endpoint(extended)?.end ?? null, sourceUnchanged: endpoint(line)?.end?.x === 10 },
trim: { end: endpoint(trimmed)?.end ?? null, constraintTypes: trimmed.constraints.map((constraint) => constraint.type), sourceUnchanged: endpoint(trimLine)?.end?.x === 10 },
construction: { enabled: construction.geometry[0]?.construction === true, sourceUnchanged: line.geometry[0]?.construction !== true },
autoConstraint: { suggestionReasons: suggestions.map((suggestion) => suggestion.reason), appliedIds: auto.constraints.map((constraint) => constraint.id), sourceUnchanged: base.constraints.length === 0 },
replay: { applied: replay.applied, undone: replay.undone, redone: replay.redone, finalEnd: endpoint(replay.snapshot)?.end ?? null },
}
const interactionLine = endpoint(interaction.getSnapshot())
const interactionState = interaction.getState()
report.interactions = { nativeEvents, start: interactionLine?.start ?? null, end: interactionLine?.end ?? null, tool: interactionState.tool, pointerActive: interactionState.pointerActive, applied: interactionState.applied, undone: interactionState.undone, redone: interactionState.redone }
report.opfs = { markerSuite: markerPayload.suite, markerOperations: markerPayload.operations, markerRemoved }
report.status = report.crossOriginIsolated && report.operations.drag.start?.y === 2 && report.operations.drag.end?.x === 8 && report.operations.drag.end?.y === 2 && report.operations.drag.sourceUnchanged && JSON.stringify(report.operations.split.ids) === JSON.stringify(['line', 'line-2']) && JSON.stringify(report.operations.split.constraintTypes) === JSON.stringify(['coincident']) && report.operations.extend.end?.x === 15 && report.operations.extend.sourceUnchanged && report.operations.trim.end?.x === 7 && JSON.stringify(report.operations.trim.constraintTypes) === JSON.stringify(['pointOnObject']) && report.operations.trim.sourceUnchanged && report.operations.construction.enabled && report.operations.construction.sourceUnchanged && JSON.stringify(report.operations.autoConstraint.suggestionReasons) === JSON.stringify(['coincident', 'horizontal']) && report.operations.autoConstraint.appliedIds.length === 2 && report.operations.autoConstraint.sourceUnchanged && report.operations.replay.applied === 2 && report.operations.replay.undone === 1 && report.operations.replay.redone === 1 && report.operations.replay.finalEnd?.x === 6 && report.interactions.nativeEvents.pointer === 3 && report.interactions.nativeEvents.keyboard === 3 && report.interactions.start?.y === 2 && report.interactions.end?.x === 8 && report.interactions.end?.y === 2 && report.interactions.tool === 'select' && report.interactions.pointerActive === false && report.interactions.applied === 1 && report.interactions.undone === 1 && report.interactions.redone === 1 && report.opfs.markerSuite === 'SK-09' && report.opfs.markerOperations === 9 && report.opfs.markerRemoved ? 'pass' : 'failed'
} catch (error) {
report.status = 'failed'
report.error = error instanceof Error ? error.message : String(error)
}
output.textContent = JSON.stringify(report, null, 2)
await fetch('/__chrome-sketcher-editor-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome Sketcher editor harness failed.')
</script>

View File

@@ -0,0 +1,32 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Sketcher golden stress harness</title>
<pre id="result">running</pre>
<script type="module">
import { runSketchStress } from '/src/facade/sketchStress.ts'
const output = document.querySelector('#result')
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
try {
const stress = runSketchStress({ models: 500, seed: 0x20260803 })
const markerDirectory = await navigator.storage.getDirectory()
const markerName = 'sketcher-stress-chrome-marker.json'
const marker = await markerDirectory.getFileHandle(markerName, { create: true })
const writable = await marker.createWritable()
await writable.write(JSON.stringify({ suite: 'SK-12', models: stress.models, solves: stress.solves }))
await writable.close()
const markerPayload = JSON.parse(await (await marker.getFile()).text())
await markerDirectory.removeEntry(markerName)
let markerRemoved = false
try { await markerDirectory.getFileHandle(markerName) } catch (error) { markerRemoved = error?.name === 'NotFoundError' }
report.stress = stress
report.opfs = { markerSuite: markerPayload.suite, markerModels: markerPayload.models, markerSolves: markerPayload.solves, markerRemoved }
report.status = report.crossOriginIsolated && stress.models === 500 && stress.solves === 1000 && stress.deterministicMismatches === 0 && stress.unexpectedResults === 0 && stress.maxIterations <= 64 && stress.maxResidual <= 1e-12 && stress.digest === 'edac8aed' && stress.durationMs <= 2_000 && report.opfs.markerSuite === 'SK-12' && report.opfs.markerModels === 500 && report.opfs.markerSolves === 1000 && report.opfs.markerRemoved ? 'pass' : 'failed'
} catch (error) {
report.status = 'failed'
report.error = error instanceof Error ? error.message : String(error)
}
output.textContent = JSON.stringify(report, null, 2)
await fetch('/__chrome-sketcher-stress-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome Sketcher stress harness failed.')
</script>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Spreadsheet harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeSpreadsheetHarness.ts"></script>

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chrome Surface Harness</title></head><body><pre id="result">running</pre><script type="module" src="/src/chromeSurfaceHarness.ts"></script></body></html>

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome TechDraw harness</title>
<pre id="result">running</pre>
<script type="module" src="/src/chromeTechDrawHarness.ts"></script>

View File

@@ -0,0 +1,59 @@
<!doctype html>
<meta charset="utf-8">
<title>Chrome Topology Harness</title>
<pre id="result">running</pre>
<script type="module">
import { BitbybitGeometryRuntime } from '/src/facade/geometryRuntime.ts'
const output = document.querySelector('#result')
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
const persistTopologyInOpfs = async (topology) => {
if (!navigator.storage?.getDirectory) return false
const root = await navigator.storage.getDirectory()
const directory = await root.getDirectoryHandle('bitbybit-topology-harness', { create: true })
const name = `topology-${crypto.randomUUID()}.json`
const payload = JSON.stringify({ adjacency: topology.adjacency, ids: topology.entries.map((entry) => entry.ref.persistentId) })
try {
const handle = await directory.getFileHandle(name, { create: true })
const writable = await handle.createWritable()
await writable.write(payload)
await writable.close()
const restored = JSON.parse(await (await handle.getFile()).text())
return JSON.stringify(restored) === JSON.stringify(JSON.parse(payload))
} finally {
await directory.removeEntry(name).catch(() => {})
}
}
try {
const runtime = new BitbybitGeometryRuntime()
report.capabilities = await runtime.initialize()
const shape = await runtime.createBox({ documentId: 'chrome-topology', documentVersion: 1, width: 10, length: 20, height: 30, originOnCenter: false })
try {
const topology = await runtime.topology(shape, 0.05)
const analyticFaces = topology.entries.filter((entry) => entry.ref.kind === 'face' && entry.signature.analytic?.type === 'Plane').length
const analyticEdges = topology.entries.filter((entry) => entry.ref.kind === 'edge' && entry.signature.analytic?.type === 'Line').length
const adjacency = topology.adjacency
const opfsTopologyRoundTrip = await persistTopologyInOpfs(topology)
report.topology = {
faces: topology.faces.length,
edges: topology.edges.length,
vertices: topology.vertices.length,
analyticFaces,
analyticEdges,
adjacencyKeys: adjacency ? Object.values(adjacency).map((map) => Object.keys(map).length) : [],
transientIndexFree: topology.entries.every((entry) => !/index|faceIndex|edgeIndex|vertexIndex/i.test(entry.ref.persistentId)),
opfsTopologyRoundTrip,
}
report.status = report.crossOriginIsolated && report.capabilities.status === 'ready' && topology.faces.length === 6 && topology.edges.length === 12 && topology.vertices.length === 8 && analyticFaces === 6 && analyticEdges === 12 && adjacency && Object.values(adjacency).every((map) => Object.keys(map).length > 0) && report.topology.transientIndexFree && opfsTopologyRoundTrip ? 'pass' : 'failed'
} finally {
await runtime.release(shape)
runtime.dispose()
}
} catch (error) {
report.status = 'failed'
report.error = error instanceof Error ? error.message : String(error)
}
output.textContent = JSON.stringify(report)
await fetch('/__topology-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
if (report.status !== 'pass') throw new Error(report.error || 'Chrome topology harness failed.')
</script>

View File

@@ -0,0 +1,13 @@
{
"name": "BitBybit CAD Studio",
"short_name": "BitBybit CAD",
"description": "Local-first FreeCAD-compatible Web CAD workspace",
"start_url": "/start",
"scope": "/",
"display": "standalone",
"background_color": "#111519",
"theme_color": "#111519",
"lang": "en",
"categories": ["productivity", "utilities"],
"icons": []
}

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,105 @@
<!doctype html>
<meta charset="utf-8">
<title>OCCT Browser Harness</title>
<pre id="result">running</pre>
<script type="module">
const output = document.querySelector('#result')
let lastWorkerEvent = null
const MiB = 1024 * 1024
const memoryBudgets = { wasmPeakBytes: 256 * MiB, wasmRetainedBytes: 16 * MiB, browserRetainedBytes: 64 * MiB, gpuLiveResources: 0 }
const measureBrowserMemory = async () => {
if (typeof performance.measureUserAgentSpecificMemory === 'function') {
try {
const measurement = await performance.measureUserAgentSpecificMemory()
return { api: 'measureUserAgentSpecificMemory', bytes: measurement.bytes }
} catch (error) {
// Chrome may expose the API while disabling it in a particular headless mode.
}
}
if (performance.memory && Number.isFinite(performance.memory.usedJSHeapSize)) return { api: 'performance.memory.usedJSHeapSize', bytes: performance.memory.usedJSHeapSize }
return { api: 'unavailable', bytes: null }
}
const waitFor = (worker, type, timeoutMs = 180000) => new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error(`${type} timeout; last=${JSON.stringify(lastWorkerEvent)}`)), timeoutMs)
const listener = (event) => {
lastWorkerEvent = event.data
if (event.data.type === 'error') { clearTimeout(timer); worker.removeEventListener('message', listener); reject(new Error(event.data.error)); return }
if (event.data.type !== type) return
clearTimeout(timer); worker.removeEventListener('message', listener); resolve(event.data)
}
const errorListener = (event) => { clearTimeout(timer); worker.removeEventListener('message', listener); worker.removeEventListener('error', errorListener); reject(new Error(event.message || `${type} worker error`)) }
worker.addEventListener('message', listener)
worker.addEventListener('error', errorListener)
})
const run = async () => {
const browserId = new URL(location.href).searchParams.get('browser') || 'unknown'
const result = { schemaVersion: 1, browserId, crossOriginIsolated: globalThis.crossOriginIsolated === true, browser: navigator.userAgent, opfs: 'pending', initialize: 'pending', history: 'pending', memory: 'pending', abort: 'pending', crashRecovery: 'pending' }
const verifyOpfs = async () => {
const opfsRoot = await navigator.storage.getDirectory()
const opfsDirectory = await opfsRoot.getDirectoryHandle('bitbybit-browser-gate', { create: true })
const opfsFile = await opfsDirectory.getFileHandle(`${browserId}.probe`, { create: true })
const opfsWritable = await opfsFile.createWritable()
await opfsWritable.write(`OPFS-${browserId}`)
await opfsWritable.close()
const opfsText = await (await opfsFile.getFile()).text()
if (opfsText !== `OPFS-${browserId}`) throw new Error('OPFS read-after-write verification failed.')
await opfsDirectory.removeEntry(`${browserId}.probe`)
return { status: 'pass', persistence: 'opfs', bytes: opfsText.length }
}
try {
result.opfs = await Promise.race([verifyOpfs(), new Promise((_, reject) => setTimeout(() => reject(new Error('OPFS API did not respond within the browser capability window.')), 5000))])
} catch (error) {
throw error
}
let worker = new Worker('/native/occt-history/browser-harness-worker.js', { type: 'module' })
worker.postMessage({ type: 'initialize' })
const ready = await waitFor(worker, 'ready')
result.initialize = ready.occtVersion
worker.postMessage({ type: 'history', requestId: 'history-1', operation: 'cut' })
const history = await waitFor(worker, 'history')
result.history = { provider: history.provider, recordCount: history.recordCount }
const stressIterations = 1000
const browserBefore = await measureBrowserMemory()
worker.postMessage({ type: 'stress', requestId: 'stress-1', iterations: stressIterations })
const stress = await waitFor(worker, 'stress')
const browserAfter = await measureBrowserMemory()
const browserRetainedBytes = browserBefore.bytes === null || browserAfter.bytes === null ? null : browserAfter.bytes - browserBefore.bytes
result.memory = {
iterations: stress.iterations,
budgets: memoryBudgets,
wasm: stress.wasm,
browser: { before: browserBefore, after: browserAfter, retainedBytes: browserRetainedBytes },
gpu: stress.gpu,
ownership: stress.ownership
}
result.memory.withinBudget =
stress.wasm.peakHeapBytes <= memoryBudgets.wasmPeakBytes &&
stress.wasm.retainedBytes <= memoryBudgets.wasmRetainedBytes &&
browserRetainedBytes !== null && browserRetainedBytes <= memoryBudgets.browserRetainedBytes &&
stress.gpu.live <= memoryBudgets.gpuLiveResources &&
stress.ownership.live === 0 && stress.ownership.created === stress.ownership.released
worker.postMessage({ type: 'cancel', requestId: 'cancel-1' })
const cancelled = await waitFor(worker, 'cancelled')
result.abort = cancelled.requestId === 'cancel-1' ? 'recovered' : 'failed'
worker.terminate()
worker = new Worker('/native/occt-history/browser-harness-worker.js', { type: 'module' })
worker.postMessage({ type: 'initialize' })
const restarted = await waitFor(worker, 'ready')
result.crashRecovery = restarted.occtVersion === ready.occtVersion ? 'recovered' : 'failed'
worker.terminate()
if (!result.crossOriginIsolated || result.opfs?.status !== 'pass' || result.opfs.persistence !== 'opfs' || result.abort !== 'recovered' || result.crashRecovery !== 'recovered' || result.memory.iterations !== stressIterations || result.memory.withinBudget !== true) throw new Error('Browser isolation, OPFS, Worker recovery or memory budget contract failed.')
return result
}
try {
const result = await run()
output.textContent = `BROWSER_OCCT_RESULT=${JSON.stringify(result)}`
await fetch('/__occt-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(result) })
} catch (error) {
const result = { status: 'failed', error: error instanceof Error ? error.message : String(error) }
output.textContent = `BROWSER_OCCT_RESULT=${JSON.stringify(result)}`
await fetch('/__occt-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(result) })
throw error
}
</script>

View File

@@ -0,0 +1,455 @@
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>planegcs browser harness</title></head>
<body>
<pre id="status">loading</pre>
<script type="module">
import { createSketch } from '/src/facade/sketcher.ts'
import { PlanegcsWorkerProvider } from '/src/facade/planegcsWorkerClient.ts'
import { SketchSolverCoordinator } from '/src/facade/sketchSolverProtocol.ts'
const browserId = new URL(location.href).searchParams.get('browser') || 'chrome'
const report = { schemaVersion: 1, browserId, executionContext: 'dedicated-worker', crossOriginIsolated, sharedArrayBuffer: typeof SharedArrayBuffer === 'function', status: 'failed' }
const worker = new Worker('/planegcs-browser-worker.js', { type: 'module', name: 'freecad-planegcs-verification' })
const provider = new PlanegcsWorkerProvider()
try {
const requestId = crypto.randomUUID()
const response = await new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('planegcs Worker solve timed out.')), 60_000)
worker.addEventListener('message', (event) => {
if (event.data?.requestId !== requestId) return
clearTimeout(timeout)
if (event.data.ok) resolve(event.data)
else reject(new Error(event.data.error || 'planegcs Worker solve failed.'))
})
worker.addEventListener('error', (event) => {
clearTimeout(timeout)
reject(new Error(event.message || 'planegcs Worker crashed.'))
}, { once: true })
worker.postMessage({ requestId, operation: 'solve-horizontal-distance', input: { startX: 0, startY: 0, endX: 3, endY: 4, targetLength: 5 } })
})
const values = response.values
const [solveStatus, startX, startY, endX, endY, residual] = values
const nativePassed = solveStatus <= 1 && Math.abs(startY - endY) <= 1e-7 && Math.abs(Math.hypot(endX - startX, endY - startY) - 5) <= 1e-7 && Math.abs(residual) <= 1e-7
const verticalRequestId = crypto.randomUUID()
const verticalResponse = await new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('planegcs vertical Worker solve timed out.')), 60_000)
const onMessage = (event) => {
if (event.data?.requestId !== verticalRequestId) return
clearTimeout(timeout)
worker.removeEventListener('message', onMessage)
if (event.data.ok) resolve(event.data)
else reject(new Error(event.data.error || 'planegcs vertical Worker solve failed.'))
}
worker.addEventListener('message', onMessage)
worker.postMessage({ requestId: verticalRequestId, operation: 'solve-vertical-distance', input: { startX: 0, startY: 0, endX: 4, endY: 3, targetLength: 5 } })
})
const verticalValues = verticalResponse.values
const [verticalSolveStatus, verticalStartX, verticalStartY, verticalEndX, verticalEndY, verticalResidual] = verticalValues
const verticalPassed = verticalSolveStatus <= 1 && Math.abs(verticalStartX - verticalEndX) <= 1e-7 && Math.abs(Math.hypot(verticalEndX - verticalStartX, verticalEndY - verticalStartY) - 5) <= 1e-7 && Math.abs(verticalResidual) <= 1e-7
const distanceXRequestId = crypto.randomUUID()
const distanceXResponse = await new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('planegcs DistanceX Worker solve timed out.')), 60_000)
const onMessage = (event) => {
if (event.data?.requestId !== distanceXRequestId) return
clearTimeout(timeout)
worker.removeEventListener('message', onMessage)
if (event.data.ok) resolve(event.data)
else reject(new Error(event.data.error || 'planegcs DistanceX Worker solve failed.'))
}
worker.addEventListener('message', onMessage)
worker.postMessage({ requestId: distanceXRequestId, operation: 'solve-distance-x', input: { startX: 0, startY: 0, endX: 3, endY: 4, targetDistance: 6 } })
})
const distanceXValues = distanceXResponse.values
const [distanceXSolveStatus, distanceXStartX, distanceXStartY, distanceXEndX, distanceXEndY, distanceXResidual] = distanceXValues
const distanceXPassed = distanceXSolveStatus <= 1 && Math.abs(distanceXEndX - distanceXStartX - 6) <= 1e-7 && Math.abs(distanceXStartY - distanceXEndY) <= 1e-7 && Math.abs(distanceXResidual) <= 1e-7
const distanceYRequestId = crypto.randomUUID()
const distanceYResponse = await new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('planegcs DistanceY Worker solve timed out.')), 60_000)
const onMessage = (event) => {
if (event.data?.requestId !== distanceYRequestId) return
clearTimeout(timeout)
worker.removeEventListener('message', onMessage)
if (event.data.ok) resolve(event.data)
else reject(new Error(event.data.error || 'planegcs DistanceY Worker solve failed.'))
}
worker.addEventListener('message', onMessage)
worker.postMessage({ requestId: distanceYRequestId, operation: 'solve-distance-y', input: { startX: 0, startY: 0, endX: 3, endY: 4, targetDistance: 6 } })
})
const distanceYValues = distanceYResponse.values
const [distanceYSolveStatus, distanceYStartX, distanceYStartY, distanceYEndX, distanceYEndY, distanceYResidual] = distanceYValues
const distanceYPassed = distanceYSolveStatus <= 1 && Math.abs(distanceYEndY - distanceYStartY - 6) <= 1e-7 && Math.abs(distanceYStartX - distanceYEndX) <= 1e-7 && Math.abs(distanceYResidual) <= 1e-7
const angleRequestId = crypto.randomUUID()
const angleResponse = await new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('planegcs Angle Worker solve timed out.')), 60_000)
const onMessage = (event) => {
if (event.data?.requestId !== angleRequestId) return
clearTimeout(timeout)
worker.removeEventListener('message', onMessage)
if (event.data.ok) resolve(event.data)
else reject(new Error(event.data.error || 'planegcs Angle Worker solve failed.'))
}
worker.addEventListener('message', onMessage)
worker.postMessage({ requestId: angleRequestId, operation: 'solve-angle', input: { startX: 0, startY: 0, endX: 3, endY: 4, targetLength: 5, targetAngle: Math.PI / 4 } })
})
const angleValues = angleResponse.values
const [angleSolveStatus, angleStartX, angleStartY, angleEndX, angleEndY, angleLengthResidual, angleResidual] = angleValues
const anglePassed = angleSolveStatus <= 1 && Math.abs(Math.hypot(angleEndX - angleStartX, angleEndY - angleStartY) - 5) <= 1e-7 && Math.abs(Math.atan2(angleEndY - angleStartY, angleEndX - angleStartX) - Math.PI / 4) <= 1e-7 && Math.abs(angleLengthResidual) <= 1e-7 && Math.abs(angleResidual) <= 1e-7
const circleRadiusRequestId = crypto.randomUUID()
const circleRadiusResponse = await new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('planegcs Circle Radius Worker solve timed out.')), 60_000)
const onMessage = (event) => {
if (event.data?.requestId !== circleRadiusRequestId) return
clearTimeout(timeout)
worker.removeEventListener('message', onMessage)
if (event.data.ok) resolve(event.data)
else reject(new Error(event.data.error || 'planegcs Circle Radius Worker solve failed.'))
}
worker.addEventListener('message', onMessage)
worker.postMessage({ requestId: circleRadiusRequestId, operation: 'solve-circle-radius', input: { centerX: 2, centerY: 3, radius: 1, targetRadius: 4 } })
})
const circleRadiusValues = circleRadiusResponse.values
const [circleRadiusSolveStatus, circleCenterX, circleCenterY, circleRadius, circleRadiusResidual] = circleRadiusValues
const circleRadiusPassed = circleRadiusSolveStatus <= 1 && Math.abs(circleCenterX - 2) <= 1e-7 && Math.abs(circleCenterY - 3) <= 1e-7 && Math.abs(circleRadius - 4) <= 1e-7 && Math.abs(circleRadiusResidual) <= 1e-7
const circleDiameterRequestId = crypto.randomUUID()
const circleDiameterResponse = await new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('planegcs Circle Diameter Worker solve timed out.')), 60_000)
const onMessage = (event) => {
if (event.data?.requestId !== circleDiameterRequestId) return
clearTimeout(timeout)
worker.removeEventListener('message', onMessage)
if (event.data.ok) resolve(event.data)
else reject(new Error(event.data.error || 'planegcs Circle Diameter Worker solve failed.'))
}
worker.addEventListener('message', onMessage)
worker.postMessage({ requestId: circleDiameterRequestId, operation: 'solve-circle-diameter', input: { centerX: 2, centerY: 3, radius: 1, targetDiameter: 8 } })
})
const circleDiameterValues = circleDiameterResponse.values
const [circleDiameterSolveStatus, circleDiameterCenterX, circleDiameterCenterY, circleDiameterRadius, circleDiameterResidual] = circleDiameterValues
const circleDiameterPassed = circleDiameterSolveStatus <= 1 && Math.abs(circleDiameterCenterX - 2) <= 1e-7 && Math.abs(circleDiameterCenterY - 3) <= 1e-7 && Math.abs(circleDiameterRadius * 2 - 8) <= 1e-7 && Math.abs(circleDiameterResidual) <= 1e-7
const relationInput = { firstStartX: 0, firstStartY: 0, firstEndX: 4, firstEndY: 0, secondStartX: 0, secondStartY: 2, secondEndX: 3, secondEndY: 6, secondLength: 5 }
const relationRequest = (operation, requestId, input = relationInput) => new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error(`planegcs ${operation} Worker solve timed out.`)), 60_000)
const onMessage = (event) => {
if (event.data?.requestId !== requestId) return
clearTimeout(timeout)
worker.removeEventListener('message', onMessage)
if (event.data.ok) resolve(event.data)
else reject(new Error(event.data.error || `planegcs ${operation} Worker solve failed.`))
}
worker.addEventListener('message', onMessage)
worker.postMessage({ requestId, operation, input })
})
const equalLinesResponse = await relationRequest('solve-equal-lines', crypto.randomUUID())
const equalLinesValues = equalLinesResponse.values
const [equalLinesSolveStatus, equalLinesFirstStartX, equalLinesFirstStartY, equalLinesFirstEndX, equalLinesFirstEndY, equalLinesSecondStartX, equalLinesSecondStartY, equalLinesSecondEndX, equalLinesSecondEndY, equalLinesResidual] = equalLinesValues
const equalLinesPassed = equalLinesSolveStatus <= 1 && Math.abs(Math.hypot(equalLinesFirstEndX - equalLinesFirstStartX, equalLinesFirstEndY - equalLinesFirstStartY) - Math.hypot(equalLinesSecondEndX - equalLinesSecondStartX, equalLinesSecondEndY - equalLinesSecondStartY)) <= 1e-7 && Math.abs(equalLinesResidual) <= 1e-7
const equalCirclesResponse = await relationRequest('solve-equal-circles', crypto.randomUUID(), { firstCenterX: 2, firstCenterY: 3, firstRadius: 4, secondCenterX: 8, secondCenterY: 9, secondRadius: 1 })
const equalCirclesValues = equalCirclesResponse.values
const [equalCirclesSolveStatus, equalCirclesFirstCenterX, equalCirclesFirstCenterY, equalCirclesFirstRadius, equalCirclesSecondCenterX, equalCirclesSecondCenterY, equalCirclesSecondRadius, equalCirclesResidual] = equalCirclesValues
const equalCirclesPassed = equalCirclesSolveStatus <= 1 && Math.abs(equalCirclesFirstCenterX - 2) <= 1e-7 && Math.abs(equalCirclesFirstCenterY - 3) <= 1e-7 && Math.abs(equalCirclesFirstRadius - 4) <= 1e-7 && Math.abs(equalCirclesSecondCenterX - 8) <= 1e-7 && Math.abs(equalCirclesSecondCenterY - 9) <= 1e-7 && Math.abs(equalCirclesSecondRadius - 4) <= 1e-7 && Math.abs(equalCirclesResidual) <= 1e-7
const tangentCirclesResponse = await relationRequest('solve-tangent-circles', crypto.randomUUID(), { firstCenterX: 2, firstCenterY: 3, firstRadius: 4, secondCenterX: 10, secondCenterY: 3, secondRadius: 1 })
const tangentCirclesValues = tangentCirclesResponse.values
const [tangentCirclesSolveStatus, tangentCirclesFirstCenterX, tangentCirclesFirstCenterY, tangentCirclesFirstRadius, tangentCirclesSecondCenterX, tangentCirclesSecondCenterY, tangentCirclesSecondRadius, tangentCirclesResidual] = tangentCirclesValues
const tangentCirclesPassed = tangentCirclesSolveStatus <= 1 && Math.abs(tangentCirclesFirstCenterX - 2) <= 1e-7 && Math.abs(tangentCirclesFirstCenterY - 3) <= 1e-7 && Math.abs(tangentCirclesFirstRadius - 4) <= 1e-7 && Math.abs(tangentCirclesSecondCenterX - 10) <= 1e-7 && Math.abs(tangentCirclesSecondCenterY - 3) <= 1e-7 && Math.abs(tangentCirclesSecondRadius - 4) <= 1e-7 && Math.abs(tangentCirclesResidual) <= 1e-7
const pointSymmetryResponse = await relationRequest('solve-point-symmetry', crypto.randomUUID(), { firstX: 1, firstY: 2, secondX: 0, secondY: 0, centerX: 4, centerY: 6 })
const pointSymmetryValues = pointSymmetryResponse.values
const [pointSymmetrySolveStatus, pointSymmetryFirstX, pointSymmetryFirstY, pointSymmetrySecondX, pointSymmetrySecondY, pointSymmetryCenterX, pointSymmetryCenterY, pointSymmetryResidual] = pointSymmetryValues
const pointSymmetryPassed = pointSymmetrySolveStatus <= 1 && Math.abs(pointSymmetryFirstX - 1) <= 1e-7 && Math.abs(pointSymmetryFirstY - 2) <= 1e-7 && Math.abs(pointSymmetrySecondX - 7) <= 1e-7 && Math.abs(pointSymmetrySecondY - 10) <= 1e-7 && Math.abs(pointSymmetryCenterX - 4) <= 1e-7 && Math.abs(pointSymmetryCenterY - 6) <= 1e-7 && Math.abs(pointSymmetryResidual) <= 1e-7
const pointOnLineResponse = await relationRequest('solve-point-on-line', crypto.randomUUID(), { pointX: 2, pointY: 3, startX: 0, startY: 0, endX: 4, endY: 0, vertical: false })
const pointOnLineValues = pointOnLineResponse.values
const [pointOnLineSolveStatus, pointOnLineX, pointOnLineY, pointOnLineStartX, pointOnLineStartY, pointOnLineEndX, pointOnLineEndY, pointOnLineResidual] = pointOnLineValues
const pointOnLinePassed = pointOnLineSolveStatus <= 1 && Math.abs(pointOnLineX - 2) <= 1e-7 && Math.abs(pointOnLineY) <= 1e-7 && Math.abs(pointOnLineStartX) <= 1e-7 && Math.abs(pointOnLineStartY) <= 1e-7 && Math.abs(pointOnLineEndX - 4) <= 1e-7 && Math.abs(pointOnLineEndY) <= 1e-7 && Math.abs(pointOnLineResidual) <= 1e-7
const pointOnCircleResponse = await relationRequest('solve-point-on-circle', crypto.randomUUID(), { pointX: 4, pointY: 6, centerX: 2, centerY: 3, radius: 4 })
const pointOnCircleValues = pointOnCircleResponse.values
const [pointOnCircleSolveStatus, pointOnCircleX, pointOnCircleY, pointOnCircleCenterX, pointOnCircleCenterY, pointOnCircleRadius, pointOnCircleResidual] = pointOnCircleValues
const pointOnCirclePassed = pointOnCircleSolveStatus <= 1 && Math.abs(pointOnCircleX - 4) <= 1e-7 && Math.abs(pointOnCircleY - 6.464101615137754) <= 1e-7 && Math.abs(pointOnCircleCenterX - 2) <= 1e-7 && Math.abs(pointOnCircleCenterY - 3) <= 1e-7 && Math.abs(pointOnCircleRadius - 4) <= 1e-7 && Math.abs(pointOnCircleResidual) <= 1e-7
const pointOnArcResponse = await relationRequest('solve-point-on-arc', crypto.randomUUID(), { pointX: 2, pointY: 6, centerX: 2, centerY: 3, radius: 4, startAngle: 0, endAngle: Math.PI })
const pointOnArcValues = pointOnArcResponse.values
const [pointOnArcSolveStatus, pointOnArcX, pointOnArcY, pointOnArcCenterX, pointOnArcCenterY, pointOnArcRadius, pointOnArcStartAngle, pointOnArcEndAngle, pointOnArcResidual] = pointOnArcValues
const pointOnArcPassed = pointOnArcSolveStatus <= 1 && Math.abs(pointOnArcX - 2) <= 1e-7 && Math.abs(pointOnArcY - 7) <= 1e-7 && Math.abs(pointOnArcCenterX - 2) <= 1e-7 && Math.abs(pointOnArcCenterY - 3) <= 1e-7 && Math.abs(pointOnArcRadius - 4) <= 1e-7 && Math.abs(pointOnArcStartAngle) <= 1e-7 && Math.abs(pointOnArcEndAngle - Math.PI) <= 1e-7 && Math.abs(pointOnArcResidual) <= 1e-7
const pointOnEllipseResponse = await relationRequest('solve-point-on-ellipse', crypto.randomUUID(), { pointX: 2, pointY: 7, centerX: 2, centerY: 3, focusX: 6, focusY: 3, minorRadius: 3 })
const pointOnEllipseValues = pointOnEllipseResponse.values
const [pointOnEllipseSolveStatus, pointOnEllipseX, pointOnEllipseY, pointOnEllipseCenterX, pointOnEllipseCenterY, pointOnEllipseFocusX, pointOnEllipseFocusY, pointOnEllipseMinorRadius, pointOnEllipseResidual] = pointOnEllipseValues
const pointOnEllipsePassed = pointOnEllipseSolveStatus <= 1 && Math.abs(pointOnEllipseX - 2) <= 1e-7 && Math.abs(pointOnEllipseY - 6) <= 1e-7 && Math.abs(pointOnEllipseCenterX - 2) <= 1e-7 && Math.abs(pointOnEllipseCenterY - 3) <= 1e-7 && Math.abs(pointOnEllipseFocusX - 6) <= 1e-7 && Math.abs(pointOnEllipseFocusY - 3) <= 1e-7 && Math.abs(pointOnEllipseMinorRadius - 3) <= 1e-7 && Math.abs(pointOnEllipseResidual) <= 1e-7
const pointOnBsplineResponse = await relationRequest('solve-point-on-cubic-bspline', crypto.randomUUID(), { pointX: 2, pointY: 1, pole0X: 0, pole0Y: 0, pole1X: 1, pole1Y: 2, pole2X: 3, pole2Y: 2, pole3X: 4, pole3Y: 0, pointParameter: 0.4 })
const pointOnBsplineValues = pointOnBsplineResponse.values
const [pointOnBsplineSolveStatus, pointOnBsplineX, pointOnBsplineY, pointOnBsplineParameter, pointOnBsplineResidualX, pointOnBsplineResidualY] = pointOnBsplineValues
const pointOnBsplinePassed = pointOnBsplineSolveStatus <= 1 && Math.abs(pointOnBsplineX - 2) <= 1e-7 && Math.abs(pointOnBsplineY - 1.5) <= 1e-7 && Math.abs(pointOnBsplineParameter - 0.5) <= 1e-7 && Math.abs(pointOnBsplineResidualX) <= 1e-7 && Math.abs(pointOnBsplineResidualY) <= 1e-7
const bsplineWeightResponse = await relationRequest('solve-cubic-bspline-weight', crypto.randomUUID(), { pole0X: 0, pole0Y: 0, pole1X: 1, pole1Y: 2, pole2X: 3, pole2Y: 2, pole3X: 4, pole3Y: 0, weight0: 1, weight1: 0.75, weight2: 1.25, weight3: 1, controlPointIndex: 1, targetWeight: 1.5 })
const [bsplineWeightSolveStatus, bsplineWeight0, bsplineWeight1, bsplineWeight2, bsplineWeight3, bsplineWeightHelperX, bsplineWeightHelperY, bsplineWeightHelperRadius, bsplineWeightAlignmentResidual, bsplineWeightResidual] = bsplineWeightResponse.values
const bsplineWeightPassed = bsplineWeightSolveStatus <= 1 && Math.abs(bsplineWeight0 - 1) <= 1e-7 && Math.abs(bsplineWeight1 - 1.5) <= 1e-7 && Math.abs(bsplineWeight2 - 1.25) <= 1e-7 && Math.abs(bsplineWeight3 - 1) <= 1e-7 && Math.abs(bsplineWeightHelperX - 1) <= 1e-7 && Math.abs(bsplineWeightHelperY - 2) <= 1e-7 && Math.abs(bsplineWeightHelperRadius - 1.5) <= 1e-7 && Math.abs(bsplineWeightAlignmentResidual) <= 1e-7 && Math.abs(bsplineWeightResidual) <= 1e-7
const parallelResponse = await relationRequest('solve-parallel-lines', crypto.randomUUID())
const parallelValues = parallelResponse.values
const [parallelSolveStatus, parallelFirstStartX, parallelFirstStartY, parallelFirstEndX, parallelFirstEndY, parallelSecondStartX, parallelSecondStartY, parallelSecondEndX, parallelSecondEndY, parallelResidual] = parallelValues
const parallelPassed = parallelSolveStatus <= 1 && Math.abs(parallelFirstEndY - parallelFirstStartY) <= 1e-7 && Math.abs(parallelSecondEndY - parallelSecondStartY) <= 1e-7 && Math.abs(parallelSecondEndX - 5) <= 1e-7 && Math.abs(parallelResidual) <= 1e-7
const perpendicularResponse = await relationRequest('solve-perpendicular-lines', crypto.randomUUID())
const perpendicularValues = perpendicularResponse.values
const [perpendicularSolveStatus, perpendicularFirstStartX, perpendicularFirstStartY, perpendicularFirstEndX, perpendicularFirstEndY, perpendicularSecondStartX, perpendicularSecondStartY, perpendicularSecondEndX, perpendicularSecondEndY, perpendicularResidual] = perpendicularValues
const perpendicularPassed = perpendicularSolveStatus <= 1 && Math.abs(perpendicularFirstEndY - perpendicularFirstStartY) <= 1e-7 && Math.abs(perpendicularSecondEndX - perpendicularSecondStartX) <= 1e-7 && Math.abs(perpendicularSecondEndY - 7) <= 1e-7 && Math.abs(perpendicularResidual) <= 1e-7
const coincidentResponse = await relationRequest('solve-coincident-lines', crypto.randomUUID())
const coincidentValues = coincidentResponse.values
const [coincidentSolveStatus, coincidentFirstStartX, coincidentFirstStartY, coincidentFirstEndX, coincidentFirstEndY, coincidentSecondStartX, coincidentSecondStartY, coincidentSecondEndX, coincidentSecondEndY, coincidentResidual] = coincidentValues
const coincidentPassed = coincidentSolveStatus <= 1 && Math.abs(coincidentFirstEndX - coincidentSecondStartX) <= 1e-7 && Math.abs(coincidentFirstEndY - coincidentSecondStartY) <= 1e-7 && Math.abs(coincidentResidual) <= 1e-7
const coincidentEndpointSolves = []
for (const firstPoint of ['start', 'end']) {
for (const secondPoint of ['start', 'end']) {
const firstEnd = firstPoint === 'end'
const secondEnd = secondPoint === 'end'
const endpointResponse = await relationRequest('solve-coincident-line-points', crypto.randomUUID(), { ...relationInput, firstEnd, secondEnd })
const [endpointSolveStatus, endpointFirstStartX, endpointFirstStartY, endpointFirstEndX, endpointFirstEndY, endpointSecondStartX, endpointSecondStartY, endpointSecondEndX, endpointSecondEndY, endpointResidual] = endpointResponse.values
const first = firstEnd ? { x: endpointFirstEndX, y: endpointFirstEndY } : { x: endpointFirstStartX, y: endpointFirstStartY }
const second = secondEnd ? { x: endpointSecondEndX, y: endpointSecondEndY } : { x: endpointSecondStartX, y: endpointSecondStartY }
coincidentEndpointSolves.push({ firstPoint, secondPoint, solveStatus: endpointSolveStatus, first, second, secondLength: Math.hypot(endpointSecondEndX - endpointSecondStartX, endpointSecondEndY - endpointSecondStartY), residual: endpointResidual })
}
}
const coincidentEndpointSolvesPassed = coincidentEndpointSolves.length === 4 && coincidentEndpointSolves.every((entry) => entry.solveStatus <= 1 && Math.abs(entry.first.x - entry.second.x) <= 1e-7 && Math.abs(entry.first.y - entry.second.y) <= 1e-7 && Math.abs(entry.secondLength - 5) <= 1e-7 && Math.abs(entry.residual) <= 1e-7)
const snellsLawResponse = await relationRequest('solve-snells-law-lines', crypto.randomUUID(), { firstStartX: 0, firstStartY: 0, firstEndX: -4, firstEndY: 4, secondStartX: 0, secondStartY: 0, secondEndX: 4, secondEndY: 3, boundaryStartX: -5, boundaryStartY: 0, boundaryEndX: 5, boundaryEndY: 0, refractiveRatio: 1.5, firstEnd: false, secondEnd: false })
const [snellsLawSolveStatus, snellsFirstStartX, snellsFirstStartY, snellsFirstEndX, snellsFirstEndY, snellsSecondStartX, snellsSecondStartY, snellsSecondEndX, snellsSecondEndY, snellsBoundaryStartX, snellsBoundaryStartY, snellsBoundaryEndX, snellsBoundaryEndY, snellsLawResidual] = snellsLawResponse.values
const snellsLawPassed = snellsLawSolveStatus <= 1 && Math.abs(snellsFirstStartX - snellsSecondStartX) <= 1e-7 && Math.abs(snellsFirstStartY - snellsSecondStartY) <= 1e-7 && Math.abs(Math.hypot(snellsSecondEndX - snellsSecondStartX, snellsSecondEndY - snellsSecondStartY) - 5) <= 1e-7 && Math.abs(snellsBoundaryStartY) <= 1e-7 && Math.abs(snellsBoundaryEndY) <= 1e-7 && Math.abs(snellsLawResidual) <= 1e-7
const ellipseInternalAlignmentSolves = []
for (const alignmentType of [1, 2, 3, 4]) {
const response = await relationRequest('solve-ellipse-internal-alignment', crypto.randomUUID(), { centerX: 2, centerY: 3, majorRadius: 5, minorRadius: 3, rotation: 0.25, alignmentType })
const [solveStatus, centerX, centerY, focusX, focusY, minorRadius, helperStartX, helperStartY, helperEndX, helperEndY, residual] = response.values
const focalDistance = 4
const major = { x: Math.cos(0.25), y: Math.sin(0.25) }
const minor = { x: -major.y, y: major.x }
const expectedStart = alignmentType === 1 ? { x: 2 + 5 * major.x, y: 3 + 5 * major.y } : alignmentType === 2 ? { x: 2 + 3 * minor.x, y: 3 + 3 * minor.y } : { x: 2 + (alignmentType === 3 ? 1 : -1) * focalDistance * major.x, y: 3 + (alignmentType === 3 ? 1 : -1) * focalDistance * major.y }
const expectedEnd = alignmentType === 1 ? { x: 2 - 5 * major.x, y: 3 - 5 * major.y } : alignmentType === 2 ? { x: 2 - 3 * minor.x, y: 3 - 3 * minor.y } : expectedStart
const helperError = Math.max(Math.abs(helperStartX - expectedStart.x), Math.abs(helperStartY - expectedStart.y), Math.abs(helperEndX - expectedEnd.x), Math.abs(helperEndY - expectedEnd.y))
ellipseInternalAlignmentSolves.push({ alignmentType, solveStatus, center: { x: centerX, y: centerY }, focus: { x: focusX, y: focusY }, minorRadius, helperStart: { x: helperStartX, y: helperStartY }, helperEnd: { x: helperEndX, y: helperEndY }, helperError, residual })
}
const ellipseInternalAlignmentSolvesPassed = ellipseInternalAlignmentSolves.length === 4 && ellipseInternalAlignmentSolves.every((entry, index) => entry.alignmentType === index + 1 && entry.solveStatus <= 1 && Math.abs(entry.center.x - 2) <= 1e-7 && Math.abs(entry.center.y - 3) <= 1e-7 && Math.abs(entry.minorRadius - 3) <= 1e-7 && entry.helperError <= 1e-7 && Math.abs(entry.residual) <= 1e-7)
const capabilities = await provider.initialize()
const snapshot = createSketch('chrome-provider', [{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 3, y: 4 } }], [
{ id: 'horizontal', type: 'horizontal', geometryId: 'line' },
{ id: 'length', type: 'distance', first: { geometryId: 'line', point: 'start' }, second: { geometryId: 'line', point: 'end' }, value: 5 },
])
const request = (requestId, requestSnapshot = snapshot) => ({ requestId, documentId: 'chrome-doc', documentVersion: 1, snapshot: requestSnapshot })
const completed = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-completed'))
const verticalSnapshot = createSketch('chrome-provider-vertical', [{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 4, y: 3 } }], [
{ id: 'vertical', type: 'vertical', geometryId: 'line' },
{ id: 'length', type: 'distance', first: { geometryId: 'line', point: 'start' }, second: { geometryId: 'line', point: 'end' }, value: 5 },
])
const verticalCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-vertical-completed', verticalSnapshot))
const parallelSnapshot = createSketch('chrome-provider-parallel', [
{ id: 'first', type: 'line', start: { x: 0, y: 0 }, end: { x: 4, y: 0 } },
{ id: 'second', type: 'line', start: { x: 0, y: 2 }, end: { x: 3, y: 6 } },
], [{ id: 'parallel', type: 'parallel', firstGeometryId: 'first', secondGeometryId: 'second' }])
const parallelCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-parallel-completed', parallelSnapshot))
const perpendicularSnapshot = createSketch('chrome-provider-perpendicular', [
{ id: 'first', type: 'line', start: { x: 0, y: 0 }, end: { x: 4, y: 0 } },
{ id: 'second', type: 'line', start: { x: 0, y: 2 }, end: { x: 3, y: 6 } },
], [{ id: 'perpendicular', type: 'perpendicular', firstGeometryId: 'first', secondGeometryId: 'second' }])
const perpendicularCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-perpendicular-completed', perpendicularSnapshot))
const coincidentSnapshot = createSketch('chrome-provider-coincident', [
{ id: 'first', type: 'line', start: { x: 0, y: 0 }, end: { x: 4, y: 0 } },
{ id: 'second', type: 'line', start: { x: 0, y: 2 }, end: { x: 3, y: 6 } },
], [{ id: 'coincident', type: 'coincident', first: { geometryId: 'first', point: 'end' }, second: { geometryId: 'second', point: 'start' } }])
const coincidentCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-coincident-completed', coincidentSnapshot))
const coincidentEndpointProviders = []
for (const firstPoint of ['start', 'end']) {
for (const secondPoint of ['start', 'end']) {
const endpointSnapshot = createSketch(`chrome-provider-coincident-${firstPoint}-${secondPoint}`, [
{ id: 'first', type: 'line', start: { x: 0, y: 0 }, end: { x: 4, y: 0 } },
{ id: 'second', type: 'line', start: { x: 0, y: 2 }, end: { x: 3, y: 6 } },
], [{ id: 'coincident', type: 'coincident', first: { geometryId: 'first', point: firstPoint }, second: { geometryId: 'second', point: secondPoint } }])
const completed = await new SketchSolverCoordinator(() => 1).solve(provider, request(`provider-coincident-${firstPoint}-${secondPoint}-completed`, endpointSnapshot))
const firstLine = completed.response?.result.snapshot.geometry[0]
const secondLine = completed.response?.result.snapshot.geometry[1]
coincidentEndpointProviders.push({
firstPoint,
secondPoint,
completed: completed.status,
first: firstLine?.type === 'line' ? firstLine[firstPoint] : null,
second: secondLine?.type === 'line' ? secondLine[secondPoint] : null,
secondLength: secondLine?.type === 'line' ? Math.hypot(secondLine.end.x - secondLine.start.x, secondLine.end.y - secondLine.start.y) : null,
})
}
}
const coincidentEndpointProvidersPassed = coincidentEndpointProviders.length === 4 && coincidentEndpointProviders.every((entry) => entry.completed === 'completed' && entry.first && entry.second && Math.abs(entry.first.x - entry.second.x) <= 1e-7 && Math.abs(entry.first.y - entry.second.y) <= 1e-7 && Math.abs(entry.secondLength - 5) <= 1e-7)
const snellsLawSnapshot = createSketch('chrome-provider-snells-law', [
{ id: 'incident', type: 'line', start: { x: 0, y: 0 }, end: { x: -4, y: 4 } },
{ id: 'refracted', type: 'line', start: { x: 0, y: 0 }, end: { x: 4, y: 3 } },
{ id: 'boundary', type: 'line', start: { x: -5, y: 0 }, end: { x: 5, y: 0 } },
], [
{ id: 'junction', type: 'coincident', first: { geometryId: 'incident', point: 'start' }, second: { geometryId: 'refracted', point: 'start' } },
{ id: 'boundary-point', type: 'pointOnObject', point: { geometryId: 'incident', point: 'start' }, geometryId: 'boundary' },
{ id: 'snells-law', type: 'snellsLaw', first: { geometryId: 'incident', point: 'start' }, second: { geometryId: 'refracted', point: 'start' }, boundaryGeometryId: 'boundary', value: 1.5 },
])
const snellsLawCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-snells-law-completed', snellsLawSnapshot))
const snellsLawProviderFirst = snellsLawCompleted.response?.result.snapshot.geometry[0]
const snellsLawProviderSecond = snellsLawCompleted.response?.result.snapshot.geometry[1]
const snellsLawProviderBoundary = snellsLawCompleted.response?.result.snapshot.geometry[2]
const snellsLawProviderPassed = snellsLawCompleted.status === 'completed' && snellsLawCompleted.response?.result.degreesOfFreedom === 8 && snellsLawProviderFirst?.type === 'line' && snellsLawProviderSecond?.type === 'line' && snellsLawProviderBoundary?.type === 'line' && Math.abs(snellsLawProviderFirst.start.x - snellsLawProviderSecond.start.x) <= 1e-7 && Math.abs(snellsLawProviderFirst.start.y - snellsLawProviderSecond.start.y) <= 1e-7 && Math.abs(Math.hypot(snellsLawProviderSecond.end.x - snellsLawProviderSecond.start.x, snellsLawProviderSecond.end.y - snellsLawProviderSecond.start.y) - 5) <= 1e-7 && Math.abs(snellsLawProviderBoundary.start.y) <= 1e-7 && Math.abs(snellsLawProviderBoundary.end.y) <= 1e-7 && Math.abs(snellsLawCompleted.response?.result.residual ?? Infinity) <= 1e-7
const ellipseInternalAlignmentSnapshot = createSketch('chrome-provider-ellipse-internal-alignment', [
{ id: 'ellipse', type: 'ellipse', center: { x: 2, y: 3 }, majorRadius: 5, minorRadius: 3, rotation: 0.25 },
], [
{ id: 'major', type: 'internalAlignment', geometryId: 'ellipse', internalGeometryIndex: 0, alignmentType: 'ellipse-major' },
{ id: 'minor', type: 'internalAlignment', geometryId: 'ellipse', internalGeometryIndex: 0, alignmentType: 'ellipse-minor' },
{ id: 'focus-1', type: 'internalAlignment', geometryId: 'ellipse', internalGeometryIndex: 0, alignmentType: 'ellipse-focus' },
{ id: 'focus-2', type: 'internalAlignment', geometryId: 'ellipse', internalGeometryIndex: 1, alignmentType: 'ellipse-focus' },
])
const ellipseInternalAlignmentCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-ellipse-internal-alignment-completed', ellipseInternalAlignmentSnapshot))
const ellipseInternalAlignmentProviderGeometry = ellipseInternalAlignmentCompleted.response?.result.snapshot.geometry[0]
const ellipseInternalAlignmentProviderPassed = ellipseInternalAlignmentCompleted.status === 'completed' && ellipseInternalAlignmentCompleted.response?.result.degreesOfFreedom === 5 && Math.abs(ellipseInternalAlignmentCompleted.response?.result.residual ?? Infinity) <= 1e-7 && ellipseInternalAlignmentProviderGeometry?.type === 'ellipse' && ellipseInternalAlignmentProviderGeometry.center.x === 2 && ellipseInternalAlignmentProviderGeometry.center.y === 3 && ellipseInternalAlignmentProviderGeometry.majorRadius === 5 && ellipseInternalAlignmentProviderGeometry.minorRadius === 3 && ellipseInternalAlignmentProviderGeometry.rotation === 0.25
const bsplineWeightSnapshot = createSketch('chrome-provider-B-spline-weight', [
{ id: 'bspline', type: 'bspline', degree: 3, controlPoints: [{ x: 0, y: 0 }, { x: 1, y: 2 }, { x: 3, y: 2 }, { x: 4, y: 0 }], weights: [1, 0.75, 1.25, 1], knots: [0, 0, 0, 0, 1, 1, 1, 1], periodic: false },
], [{ id: 'weight', type: 'weight', geometryId: 'bspline', controlPointIndex: 1, value: 1.5 }])
const bsplineWeightCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-B-spline-weight-completed', bsplineWeightSnapshot))
const bsplineWeightProviderGeometry = bsplineWeightCompleted.response?.result.snapshot.geometry[0]
const bsplineWeightProviderPassed = bsplineWeightCompleted.status === 'completed' && bsplineWeightCompleted.response?.result.degreesOfFreedom === 11 && Math.abs(bsplineWeightCompleted.response?.result.residual ?? Infinity) <= 1e-7 && bsplineWeightProviderGeometry?.type === 'bspline' && bsplineWeightProviderGeometry.weights.length === 4 && Math.abs(bsplineWeightProviderGeometry.weights[0] - 1) <= 1e-7 && Math.abs(bsplineWeightProviderGeometry.weights[1] - 1.5) <= 1e-7 && Math.abs(bsplineWeightProviderGeometry.weights[2] - 1.25) <= 1e-7 && Math.abs(bsplineWeightProviderGeometry.weights[3] - 1) <= 1e-7
const cancellationCoordinator = new SketchSolverCoordinator(() => 1)
const cancellation = cancellationCoordinator.solve(provider, request('provider-cancelled'))
cancellationCoordinator.cancel()
const cancelled = await cancellation
const recovered = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-recovered'))
const stale = await new SketchSolverCoordinator(() => 2).solve(provider, request('provider-stale'))
const recoveredLine = recovered.response?.result.snapshot.geometry[0]
const verticalProviderLine = verticalCompleted.response?.result.snapshot.geometry[0]
const parallelProviderLine = parallelCompleted.response?.result.snapshot.geometry[1]
const perpendicularProviderLine = perpendicularCompleted.response?.result.snapshot.geometry[1]
const coincidentProviderLine = coincidentCompleted.response?.result.snapshot.geometry[1]
const angleSnapshot = createSketch('chrome-provider-angle', [{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 3, y: 4 } }], [
{ id: 'angle', type: 'angle', geometryId: 'line', value: Math.PI / 4 },
{ id: 'length', type: 'distance', first: { geometryId: 'line', point: 'start' }, second: { geometryId: 'line', point: 'end' }, value: 5 },
])
const angleCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-angle-completed', angleSnapshot))
const angleProviderLine = angleCompleted.response?.result.snapshot.geometry[0]
const circleRadiusSnapshot = createSketch('chrome-provider-circle-radius', [{ id: 'circle', type: 'circle', center: { x: 2, y: 3 }, radius: 1 }], [
{ id: 'radius', type: 'radius', geometryId: 'circle', value: 4 },
])
const circleRadiusCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-circle-radius-completed', circleRadiusSnapshot))
const circleProvider = circleRadiusCompleted.response?.result.snapshot.geometry[0]
const circleDiameterSnapshot = createSketch('chrome-provider-circle-diameter', [{ id: 'circle', type: 'circle', center: { x: 2, y: 3 }, radius: 1 }], [
{ id: 'diameter', type: 'diameter', geometryId: 'circle', value: 8 },
])
const circleDiameterCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-circle-diameter-completed', circleDiameterSnapshot))
const circleDiameterProvider = circleDiameterCompleted.response?.result.snapshot.geometry[0]
const equalLinesSnapshot = createSketch('chrome-provider-equal-lines', [
{ id: 'first', type: 'line', start: { x: 0, y: 0 }, end: { x: 4, y: 0 } },
{ id: 'second', type: 'line', start: { x: 0, y: 2 }, end: { x: 3, y: 6 } },
], [{ id: 'equal', type: 'equal', firstGeometryId: 'first', secondGeometryId: 'second' }])
const equalLinesCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-equal-lines-completed', equalLinesSnapshot))
const equalLinesProviderSecond = equalLinesCompleted.response?.result.snapshot.geometry[1]
const equalCirclesSnapshot = createSketch('chrome-provider-equal-circles', [
{ id: 'first', type: 'circle', center: { x: 2, y: 3 }, radius: 4 },
{ id: 'second', type: 'circle', center: { x: 8, y: 9 }, radius: 1 },
], [{ id: 'equal', type: 'equal', firstGeometryId: 'first', secondGeometryId: 'second' }])
const equalCirclesCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-equal-circles-completed', equalCirclesSnapshot))
const equalCirclesProviderSecond = equalCirclesCompleted.response?.result.snapshot.geometry[1]
const tangentCirclesSnapshot = createSketch('chrome-provider-tangent-circles', [
{ id: 'first', type: 'circle', center: { x: 2, y: 3 }, radius: 4 },
{ id: 'second', type: 'circle', center: { x: 10, y: 3 }, radius: 1 },
], [{ id: 'tangent', type: 'tangent', firstGeometryId: 'first', secondGeometryId: 'second' }])
const tangentCirclesCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-tangent-circles-completed', tangentCirclesSnapshot))
const tangentCirclesProviderSecond = tangentCirclesCompleted.response?.result.snapshot.geometry[1]
const pointSymmetrySnapshot = createSketch('chrome-provider-point-symmetry', [
{ id: 'first', type: 'point', position: { x: 1, y: 2 } },
{ id: 'second', type: 'point', position: { x: 0, y: 0 } },
{ id: 'center', type: 'point', position: { x: 4, y: 6 } },
], [{ id: 'symmetric', type: 'symmetric', first: { geometryId: 'first', point: 'position' }, second: { geometryId: 'second', point: 'position' }, center: { geometryId: 'center', point: 'position' } }])
const pointSymmetryCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-point-symmetry-completed', pointSymmetrySnapshot))
const pointSymmetryProviderSecond = pointSymmetryCompleted.response?.result.snapshot.geometry[1]
const pointOnLineSnapshot = createSketch('chrome-provider-point-on-object', [
{ id: 'point', type: 'point', position: { x: 2, y: 3 } },
{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 4, y: 0 } },
], [{ id: 'point-on-object', type: 'pointOnObject', point: { geometryId: 'point', point: 'position' }, geometryId: 'line' }])
const pointOnLineCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-point-on-object-completed', pointOnLineSnapshot))
const pointOnLineProviderPoint = pointOnLineCompleted.response?.result.snapshot.geometry[0]
const pointOnCircleSnapshot = createSketch('chrome-provider-point-on-circle', [
{ id: 'point', type: 'point', position: { x: 4, y: 6 } },
{ id: 'circle', type: 'circle', center: { x: 2, y: 3 }, radius: 4 },
], [{ id: 'point-on-circle', type: 'pointOnObject', point: { geometryId: 'point', point: 'position' }, geometryId: 'circle' }])
const pointOnCircleCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-point-on-circle-completed', pointOnCircleSnapshot))
const pointOnCircleProviderPoint = pointOnCircleCompleted.response?.result.snapshot.geometry[0]
const pointOnCircleProviderPassed = pointOnCircleCompleted.status === 'completed' && pointOnCircleProviderPoint?.type === 'point' && Math.abs(pointOnCircleProviderPoint.position.x - 4) <= 1e-7 && Math.abs(pointOnCircleProviderPoint.position.y - 6.464101615137754) <= 1e-7
const pointOnArcSnapshot = createSketch('chrome-provider-point-on-arc', [
{ id: 'point', type: 'point', position: { x: 2, y: 6 } },
{ id: 'arc', type: 'arc', center: { x: 2, y: 3 }, radius: 4, startAngle: 0, endAngle: Math.PI },
], [{ id: 'point-on-arc', type: 'pointOnObject', point: { geometryId: 'point', point: 'position' }, geometryId: 'arc' }])
const pointOnArcCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-point-on-arc-completed', pointOnArcSnapshot))
const pointOnArcProviderPoint = pointOnArcCompleted.response?.result.snapshot.geometry[0]
const pointOnArcProviderTarget = pointOnArcCompleted.response?.result.snapshot.geometry[1]
const pointOnArcProviderPassed = pointOnArcCompleted.status === 'completed' && pointOnArcProviderPoint?.type === 'point' && Math.abs(pointOnArcProviderPoint.position.x - 2) <= 1e-7 && Math.abs(pointOnArcProviderPoint.position.y - 7) <= 1e-7 && pointOnArcProviderTarget?.type === 'arc' && Math.abs(pointOnArcProviderTarget.radius - 4) <= 1e-7 && Math.abs(pointOnArcProviderTarget.startAngle) <= 1e-7 && Math.abs(pointOnArcProviderTarget.endAngle - Math.PI) <= 1e-7
const pointOnEllipseSnapshot = createSketch('chrome-provider-point-on-ellipse', [
{ id: 'point', type: 'point', position: { x: 2, y: 7 } },
{ id: 'ellipse', type: 'ellipse', center: { x: 2, y: 3 }, majorRadius: 5, minorRadius: 3, rotation: 0 },
], [{ id: 'point-on-ellipse', type: 'pointOnObject', point: { geometryId: 'point', point: 'position' }, geometryId: 'ellipse' }])
const pointOnEllipseCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-point-on-ellipse-completed', pointOnEllipseSnapshot))
const pointOnEllipseProviderPoint = pointOnEllipseCompleted.response?.result.snapshot.geometry[0]
const pointOnEllipseProviderTarget = pointOnEllipseCompleted.response?.result.snapshot.geometry[1]
const pointOnEllipseProviderPassed = pointOnEllipseCompleted.status === 'completed' && pointOnEllipseProviderPoint?.type === 'point' && Math.abs(pointOnEllipseProviderPoint.position.x - 2) <= 1e-7 && Math.abs(pointOnEllipseProviderPoint.position.y - 6) <= 1e-7 && pointOnEllipseProviderTarget?.type === 'ellipse' && Math.abs(pointOnEllipseProviderTarget.majorRadius - 5) <= 1e-7 && Math.abs(pointOnEllipseProviderTarget.minorRadius - 3) <= 1e-7 && Math.abs(pointOnEllipseProviderTarget.rotation) <= 1e-7
const pointOnBsplineSnapshot = createSketch('chrome-provider-point-on-B-spline', [
{ id: 'point', type: 'point', position: { x: 2, y: 1 } },
{ id: 'bspline', type: 'bspline', degree: 3, controlPoints: [{ x: 0, y: 0 }, { x: 1, y: 2 }, { x: 3, y: 2 }, { x: 4, y: 0 }], weights: [1, 1, 1, 1], knots: [0, 0, 0, 0, 1, 1, 1, 1], periodic: false },
], [{ id: 'point-on-B-spline', type: 'pointOnObject', point: { geometryId: 'point', point: 'position' }, geometryId: 'bspline' }])
const pointOnBsplineCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-point-on-B-spline-completed', pointOnBsplineSnapshot))
const pointOnBsplineProviderPoint = pointOnBsplineCompleted.response?.result.snapshot.geometry[0]
const pointOnBsplineProviderTarget = pointOnBsplineCompleted.response?.result.snapshot.geometry[1]
const pointOnBsplineProviderPassed = pointOnBsplineCompleted.status === 'completed' && pointOnBsplineProviderPoint?.type === 'point' && Math.abs(pointOnBsplineProviderPoint.position.x - 2) <= 1e-7 && Math.abs(pointOnBsplineProviderPoint.position.y - 1.5) <= 1e-7 && pointOnBsplineProviderTarget?.type === 'bspline' && pointOnBsplineProviderTarget.controlPoints.length === 4
const blockSnapshot = createSketch('chrome-provider-block', [{ id: 'line', type: 'line', start: { x: 1, y: 2 }, end: { x: 4, y: 6 } }], [{ id: 'block', type: 'block', geometryId: 'line' }])
const blockCompleted = await new SketchSolverCoordinator(() => 1).solve(provider, request('provider-block-completed', blockSnapshot))
const blockProviderLine = blockCompleted.response?.result.snapshot.geometry[0]
const blockProviderPassed = blockCompleted.status === 'completed' && blockCompleted.response?.result.degreesOfFreedom === 0 && blockProviderLine?.type === 'line' && blockProviderLine.start.x === 1 && blockProviderLine.start.y === 2 && blockProviderLine.end.x === 4 && blockProviderLine.end.y === 6
const providerPassed = capabilities.availability === 'available' && capabilities.engine === 'planegcs-wasm' && capabilities.supportedGeometry.includes('point') && capabilities.supportedConstraints.includes('block') && capabilities.supportedConstraints.includes('vertical') && capabilities.supportedConstraints.includes('distanceX') && capabilities.supportedConstraints.includes('distanceY') && capabilities.supportedConstraints.includes('angle') && capabilities.supportedConstraints.includes('radius') && capabilities.supportedConstraints.includes('diameter') && capabilities.supportedConstraints.includes('equal') && capabilities.supportedConstraints.includes('tangent') && capabilities.supportedConstraints.includes('symmetric') && capabilities.supportedConstraints.includes('pointOnObject') && capabilities.supportedConstraints.includes('parallel') && capabilities.supportedConstraints.includes('perpendicular') && capabilities.supportedConstraints.includes('coincident') && completed.status === 'completed' && verticalCompleted.status === 'completed' && angleCompleted.status === 'completed' && circleRadiusCompleted.status === 'completed' && circleDiameterCompleted.status === 'completed' && equalLinesCompleted.status === 'completed' && equalCirclesCompleted.status === 'completed' && tangentCirclesCompleted.status === 'completed' && pointSymmetryCompleted.status === 'completed' && pointOnLineCompleted.status === 'completed' && parallelCompleted.status === 'completed' && perpendicularCompleted.status === 'completed' && coincidentCompleted.status === 'completed' && coincidentEndpointProvidersPassed && cancelled.status === 'cancelled' && recovered.status === 'completed' && stale.status === 'stale' && recoveredLine?.type === 'line' && Math.abs(recoveredLine.end.x - 5) <= 1e-7 && Math.abs(recoveredLine.end.y) <= 1e-7 && verticalProviderLine?.type === 'line' && Math.abs(verticalProviderLine.end.x) <= 1e-7 && Math.abs(verticalProviderLine.end.y - 5) <= 1e-7 && angleProviderLine?.type === 'line' && Math.abs(Math.hypot(angleProviderLine.end.x - angleProviderLine.start.x, angleProviderLine.end.y - angleProviderLine.start.y) - 5) <= 1e-7 && Math.abs(Math.atan2(angleProviderLine.end.y - angleProviderLine.start.y, angleProviderLine.end.x - angleProviderLine.start.x) - Math.PI / 4) <= 1e-7 && circleProvider?.type === 'circle' && Math.abs(circleProvider.center.x - 2) <= 1e-7 && Math.abs(circleProvider.center.y - 3) <= 1e-7 && Math.abs(circleProvider.radius - 4) <= 1e-7 && circleDiameterProvider?.type === 'circle' && Math.abs(circleDiameterProvider.center.x - 2) <= 1e-7 && Math.abs(circleDiameterProvider.center.y - 3) <= 1e-7 && Math.abs(circleDiameterProvider.radius * 2 - 8) <= 1e-7 && equalLinesProviderSecond?.type === 'line' && Math.abs(Math.hypot(equalLinesProviderSecond.end.x - equalLinesProviderSecond.start.x, equalLinesProviderSecond.end.y - equalLinesProviderSecond.start.y) - 4) <= 1e-7 && equalCirclesProviderSecond?.type === 'circle' && Math.abs(equalCirclesProviderSecond.radius - 4) <= 1e-7 && tangentCirclesProviderSecond?.type === 'circle' && Math.abs(tangentCirclesProviderSecond.radius - 4) <= 1e-7 && pointSymmetryProviderSecond?.type === 'point' && Math.abs(pointSymmetryProviderSecond.position.x - 7) <= 1e-7 && Math.abs(pointSymmetryProviderSecond.position.y - 10) <= 1e-7 && pointOnLineProviderPoint?.type === 'point' && Math.abs(pointOnLineProviderPoint.position.x - 2) <= 1e-7 && Math.abs(pointOnLineProviderPoint.position.y) <= 1e-7 && parallelProviderLine?.type === 'line' && Math.abs(parallelProviderLine.end.y - parallelProviderLine.start.y) <= 1e-7 && Math.abs(parallelProviderLine.end.x - 5) <= 1e-7 && perpendicularProviderLine?.type === 'line' && Math.abs(perpendicularProviderLine.end.x - perpendicularProviderLine.start.x) <= 1e-7 && Math.abs(perpendicularProviderLine.end.y - 7) <= 1e-7 && coincidentProviderLine?.type === 'line' && Math.abs(coincidentProviderLine.start.x - 4) <= 1e-7 && Math.abs(coincidentProviderLine.start.y) <= 1e-7
const snellsLawCapabilityPassed = capabilities.supportedConstraints.includes('snellsLaw')
const internalAlignmentCapabilityPassed = capabilities.supportedConstraints.includes('internalAlignment')
const weightCapabilityPassed = capabilities.supportedConstraints.includes('weight')
const arcProviderPassed = capabilities.supportedGeometry.includes('arc') && pointOnArcProviderPassed
const ellipseProviderPassed = capabilities.supportedGeometry.includes('ellipse') && pointOnEllipseProviderPassed
const bsplineProviderPassed = capabilities.supportedGeometry.includes('bspline') && pointOnBsplineProviderPassed
Object.assign(report, {
status: nativePassed && verticalPassed && distanceXPassed && distanceYPassed && anglePassed && circleRadiusPassed && circleDiameterPassed && equalLinesPassed && equalCirclesPassed && tangentCirclesPassed && pointSymmetryPassed && pointOnLinePassed && pointOnCirclePassed && pointOnCircleProviderPassed && pointOnArcPassed && arcProviderPassed && pointOnEllipsePassed && ellipseProviderPassed && pointOnBsplinePassed && bsplineProviderPassed && bsplineWeightPassed && bsplineWeightProviderPassed && weightCapabilityPassed && blockProviderPassed && parallelPassed && perpendicularPassed && coincidentPassed && coincidentEndpointSolvesPassed && snellsLawPassed && snellsLawProviderPassed && snellsLawCapabilityPassed && ellipseInternalAlignmentSolvesPassed && ellipseInternalAlignmentProviderPassed && internalAlignmentCapabilityPassed && providerPassed ? 'pass' : 'failed',
solve: { solveStatus, startX, startY, endX, endY, residual },
verticalSolve: { solveStatus: verticalSolveStatus, startX: verticalStartX, startY: verticalStartY, endX: verticalEndX, endY: verticalEndY, residual: verticalResidual },
distanceXSolve: { solveStatus: distanceXSolveStatus, startX: distanceXStartX, startY: distanceXStartY, endX: distanceXEndX, endY: distanceXEndY, residual: distanceXResidual },
distanceYSolve: { solveStatus: distanceYSolveStatus, startX: distanceYStartX, startY: distanceYStartY, endX: distanceYEndX, endY: distanceYEndY, residual: distanceYResidual },
angleSolve: { solveStatus: angleSolveStatus, startX: angleStartX, startY: angleStartY, endX: angleEndX, endY: angleEndY, lengthResidual: angleLengthResidual, residual: angleResidual, targetAngle: Math.PI / 4 },
circleRadiusSolve: { solveStatus: circleRadiusSolveStatus, centerX: circleCenterX, centerY: circleCenterY, radius: circleRadius, residual: circleRadiusResidual },
circleDiameterSolve: { solveStatus: circleDiameterSolveStatus, centerX: circleDiameterCenterX, centerY: circleDiameterCenterY, radius: circleDiameterRadius, residual: circleDiameterResidual },
equalLinesSolve: { solveStatus: equalLinesSolveStatus, firstStartX: equalLinesFirstStartX, firstStartY: equalLinesFirstStartY, firstEndX: equalLinesFirstEndX, firstEndY: equalLinesFirstEndY, secondStartX: equalLinesSecondStartX, secondStartY: equalLinesSecondStartY, secondEndX: equalLinesSecondEndX, secondEndY: equalLinesSecondEndY, residual: equalLinesResidual },
equalCirclesSolve: { solveStatus: equalCirclesSolveStatus, firstCenterX: equalCirclesFirstCenterX, firstCenterY: equalCirclesFirstCenterY, firstRadius: equalCirclesFirstRadius, secondCenterX: equalCirclesSecondCenterX, secondCenterY: equalCirclesSecondCenterY, secondRadius: equalCirclesSecondRadius, residual: equalCirclesResidual },
tangentCirclesSolve: { solveStatus: tangentCirclesSolveStatus, firstCenterX: tangentCirclesFirstCenterX, firstCenterY: tangentCirclesFirstCenterY, firstRadius: tangentCirclesFirstRadius, secondCenterX: tangentCirclesSecondCenterX, secondCenterY: tangentCirclesSecondCenterY, secondRadius: tangentCirclesSecondRadius, residual: tangentCirclesResidual },
pointSymmetrySolve: { solveStatus: pointSymmetrySolveStatus, firstX: pointSymmetryFirstX, firstY: pointSymmetryFirstY, secondX: pointSymmetrySecondX, secondY: pointSymmetrySecondY, centerX: pointSymmetryCenterX, centerY: pointSymmetryCenterY, residual: pointSymmetryResidual },
pointOnLineSolve: { solveStatus: pointOnLineSolveStatus, pointX: pointOnLineX, pointY: pointOnLineY, startX: pointOnLineStartX, startY: pointOnLineStartY, endX: pointOnLineEndX, endY: pointOnLineEndY, residual: pointOnLineResidual },
pointOnCircleSolve: { solveStatus: pointOnCircleSolveStatus, pointX: pointOnCircleX, pointY: pointOnCircleY, centerX: pointOnCircleCenterX, centerY: pointOnCircleCenterY, radius: pointOnCircleRadius, residual: pointOnCircleResidual },
pointOnArcSolve: { solveStatus: pointOnArcSolveStatus, pointX: pointOnArcX, pointY: pointOnArcY, centerX: pointOnArcCenterX, centerY: pointOnArcCenterY, radius: pointOnArcRadius, startAngle: pointOnArcStartAngle, endAngle: pointOnArcEndAngle, residual: pointOnArcResidual },
pointOnEllipseSolve: { solveStatus: pointOnEllipseSolveStatus, pointX: pointOnEllipseX, pointY: pointOnEllipseY, centerX: pointOnEllipseCenterX, centerY: pointOnEllipseCenterY, focusX: pointOnEllipseFocusX, focusY: pointOnEllipseFocusY, minorRadius: pointOnEllipseMinorRadius, residual: pointOnEllipseResidual },
pointOnBsplineSolve: { solveStatus: pointOnBsplineSolveStatus, pointX: pointOnBsplineX, pointY: pointOnBsplineY, pointParameter: pointOnBsplineParameter, residualX: pointOnBsplineResidualX, residualY: pointOnBsplineResidualY },
bsplineWeightSolve: { solveStatus: bsplineWeightSolveStatus, weights: [bsplineWeight0, bsplineWeight1, bsplineWeight2, bsplineWeight3], helper: { center: { x: bsplineWeightHelperX, y: bsplineWeightHelperY }, radius: bsplineWeightHelperRadius }, alignmentResidual: bsplineWeightAlignmentResidual, residual: bsplineWeightResidual },
parallelSolve: { solveStatus: parallelSolveStatus, firstStartX: parallelFirstStartX, firstStartY: parallelFirstStartY, firstEndX: parallelFirstEndX, firstEndY: parallelFirstEndY, secondStartX: parallelSecondStartX, secondStartY: parallelSecondStartY, secondEndX: parallelSecondEndX, secondEndY: parallelSecondEndY, residual: parallelResidual },
perpendicularSolve: { solveStatus: perpendicularSolveStatus, firstStartX: perpendicularFirstStartX, firstStartY: perpendicularFirstStartY, firstEndX: perpendicularFirstEndX, firstEndY: perpendicularFirstEndY, secondStartX: perpendicularSecondStartX, secondStartY: perpendicularSecondStartY, secondEndX: perpendicularSecondEndX, secondEndY: perpendicularSecondEndY, residual: perpendicularResidual },
coincidentSolve: { solveStatus: coincidentSolveStatus, firstEndX: coincidentFirstEndX, firstEndY: coincidentFirstEndY, secondStartX: coincidentSecondStartX, secondStartY: coincidentSecondStartY, residual: coincidentResidual },
coincidentEndpointSolves,
coincidentEndpointProviders,
snellsLawSolve: { solveStatus: snellsLawSolveStatus, firstStart: { x: snellsFirstStartX, y: snellsFirstStartY }, firstEnd: { x: snellsFirstEndX, y: snellsFirstEndY }, secondStart: { x: snellsSecondStartX, y: snellsSecondStartY }, secondEnd: { x: snellsSecondEndX, y: snellsSecondEndY }, boundaryStart: { x: snellsBoundaryStartX, y: snellsBoundaryStartY }, boundaryEnd: { x: snellsBoundaryEndX, y: snellsBoundaryEndY }, ratio: 1.5, residual: snellsLawResidual },
snellsLawProvider: { completed: snellsLawCompleted.status, degreesOfFreedom: snellsLawCompleted.response?.result.degreesOfFreedom, residual: snellsLawCompleted.response?.result.residual, first: snellsLawProviderFirst?.type === 'line' ? { start: snellsLawProviderFirst.start, end: snellsLawProviderFirst.end } : null, second: snellsLawProviderSecond?.type === 'line' ? { start: snellsLawProviderSecond.start, end: snellsLawProviderSecond.end } : null, boundary: snellsLawProviderBoundary?.type === 'line' ? { start: snellsLawProviderBoundary.start, end: snellsLawProviderBoundary.end } : null },
ellipseInternalAlignmentSolves,
ellipseInternalAlignmentProvider: { completed: ellipseInternalAlignmentCompleted.status, degreesOfFreedom: ellipseInternalAlignmentCompleted.response?.result.degreesOfFreedom, residual: ellipseInternalAlignmentCompleted.response?.result.residual, ellipse: ellipseInternalAlignmentProviderGeometry?.type === 'ellipse' ? { center: ellipseInternalAlignmentProviderGeometry.center, majorRadius: ellipseInternalAlignmentProviderGeometry.majorRadius, minorRadius: ellipseInternalAlignmentProviderGeometry.minorRadius, rotation: ellipseInternalAlignmentProviderGeometry.rotation } : null },
bsplineWeightProvider: { completed: bsplineWeightCompleted.status, degreesOfFreedom: bsplineWeightCompleted.response?.result.degreesOfFreedom, residual: bsplineWeightCompleted.response?.result.residual, bspline: bsplineWeightProviderGeometry?.type === 'bspline' ? { degree: bsplineWeightProviderGeometry.degree, controlPoints: bsplineWeightProviderGeometry.controlPoints, weights: bsplineWeightProviderGeometry.weights, knots: bsplineWeightProviderGeometry.knots, periodic: bsplineWeightProviderGeometry.periodic } : null },
pointOnCircleProvider: { completed: pointOnCircleCompleted.status, point: pointOnCircleProviderPoint?.type === 'point' ? pointOnCircleProviderPoint.position : null },
pointOnArcProvider: { completed: pointOnArcCompleted.status, point: pointOnArcProviderPoint?.type === 'point' ? pointOnArcProviderPoint.position : null, arc: pointOnArcProviderTarget?.type === 'arc' ? { center: pointOnArcProviderTarget.center, radius: pointOnArcProviderTarget.radius, startAngle: pointOnArcProviderTarget.startAngle, endAngle: pointOnArcProviderTarget.endAngle } : null },
pointOnEllipseProvider: { completed: pointOnEllipseCompleted.status, point: pointOnEllipseProviderPoint?.type === 'point' ? pointOnEllipseProviderPoint.position : null, ellipse: pointOnEllipseProviderTarget?.type === 'ellipse' ? { center: pointOnEllipseProviderTarget.center, majorRadius: pointOnEllipseProviderTarget.majorRadius, minorRadius: pointOnEllipseProviderTarget.minorRadius, rotation: pointOnEllipseProviderTarget.rotation } : null },
pointOnBsplineProvider: { completed: pointOnBsplineCompleted.status, point: pointOnBsplineProviderPoint?.type === 'point' ? pointOnBsplineProviderPoint.position : null, bspline: pointOnBsplineProviderTarget?.type === 'bspline' ? { degree: pointOnBsplineProviderTarget.degree, controlPoints: pointOnBsplineProviderTarget.controlPoints, weights: pointOnBsplineProviderTarget.weights, knots: pointOnBsplineProviderTarget.knots, periodic: pointOnBsplineProviderTarget.periodic } : null },
blockProvider: { completed: blockCompleted.status, degreesOfFreedom: blockCompleted.response?.result.degreesOfFreedom, line: blockProviderLine?.type === 'line' ? { start: blockProviderLine.start, end: blockProviderLine.end } : null },
providerLifecycle: { availability: capabilities.availability, engine: capabilities.engine, supportedGeometry: capabilities.supportedGeometry, supportedConstraints: capabilities.supportedConstraints, completed: completed.status, verticalCompleted: verticalCompleted.status, verticalEnd: verticalProviderLine?.type === 'line' ? verticalProviderLine.end : null, angleCompleted: angleCompleted.status, angleEnd: angleProviderLine?.type === 'line' ? angleProviderLine.end : null, circleRadiusCompleted: circleRadiusCompleted.status, circleRadius: circleProvider?.type === 'circle' ? { center: circleProvider.center, radius: circleProvider.radius } : null, circleDiameterCompleted: circleDiameterCompleted.status, circleDiameter: circleDiameterProvider?.type === 'circle' ? { center: circleDiameterProvider.center, radius: circleDiameterProvider.radius } : null, equalLinesCompleted: equalLinesCompleted.status, equalLinesSecond: equalLinesProviderSecond?.type === 'line' ? { start: equalLinesProviderSecond.start, end: equalLinesProviderSecond.end } : null, equalCirclesCompleted: equalCirclesCompleted.status, equalCirclesSecond: equalCirclesProviderSecond?.type === 'circle' ? { center: equalCirclesProviderSecond.center, radius: equalCirclesProviderSecond.radius } : null, tangentCirclesCompleted: tangentCirclesCompleted.status, tangentCirclesSecond: tangentCirclesProviderSecond?.type === 'circle' ? { center: tangentCirclesProviderSecond.center, radius: tangentCirclesProviderSecond.radius } : null, pointSymmetryCompleted: pointSymmetryCompleted.status, pointSymmetrySecond: pointSymmetryProviderSecond?.type === 'point' ? pointSymmetryProviderSecond.position : null, pointOnLineCompleted: pointOnLineCompleted.status, pointOnLinePoint: pointOnLineProviderPoint?.type === 'point' ? pointOnLineProviderPoint.position : null, parallelCompleted: parallelCompleted.status, parallelEnd: parallelProviderLine?.type === 'line' ? parallelProviderLine.end : null, perpendicularCompleted: perpendicularCompleted.status, perpendicularEnd: perpendicularProviderLine?.type === 'line' ? perpendicularProviderLine.end : null, coincidentCompleted: coincidentCompleted.status, coincidentStart: coincidentProviderLine?.type === 'line' ? coincidentProviderLine.start : null, cancelled: cancelled.status, recovered: recovered.status, stale: stale.status, recoveredEnd: recoveredLine?.type === 'line' ? recoveredLine.end : null },
})
} catch (error) {
report.error = error instanceof Error ? `${error.name}: ${error.message}` : String(error)
} finally {
worker.terminate()
provider.dispose()
}
document.querySelector('#status').textContent = JSON.stringify(report, null, 2)
await fetch('/__planegcs-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
</script>
</body>
</html>

View File

@@ -0,0 +1,69 @@
import createPlanegcs from '/native/planegcs/freecad-planegcs.js'
let modulePromise
const getModule = () => {
modulePromise ??= createPlanegcs()
return modulePromise
}
self.addEventListener('message', async (event) => {
const { requestId, operation, input } = event.data ?? {}
if (!requestId || !['solve-horizontal-distance', 'solve-vertical-distance', 'solve-distance-x', 'solve-distance-y', 'solve-angle', 'solve-circle-radius', 'solve-circle-diameter', 'solve-equal-lines', 'solve-equal-circles', 'solve-tangent-circles', 'solve-point-symmetry', 'solve-point-on-line', 'solve-point-on-circle', 'solve-point-on-arc', 'solve-point-on-ellipse', 'solve-point-on-cubic-bspline', 'solve-cubic-bspline-weight', 'solve-parallel-lines', 'solve-perpendicular-lines', 'solve-coincident-lines', 'solve-coincident-line-points', 'solve-snells-law-lines', 'solve-ellipse-internal-alignment'].includes(operation)) {
self.postMessage({ requestId, ok: false, error: 'Unsupported planegcs Worker request.' })
return
}
try {
const module = await getModule()
const result = operation === 'solve-vertical-distance'
? module.solveVerticalDistance(input.startX, input.startY, input.endX, input.endY, input.targetLength)
: operation === 'solve-distance-x'
? module.solveDistanceX(input.startX, input.startY, input.endX, input.endY, input.targetDistance)
: operation === 'solve-distance-y'
? module.solveDistanceY(input.startX, input.startY, input.endX, input.endY, input.targetDistance)
: operation === 'solve-angle'
? module.solveAngle(input.startX, input.startY, input.endX, input.endY, input.targetLength, input.targetAngle)
: operation === 'solve-circle-radius'
? module.solveCircleRadius(input.centerX, input.centerY, input.radius, input.targetRadius)
: operation === 'solve-circle-diameter'
? module.solveCircleDiameter(input.centerX, input.centerY, input.radius, input.targetDiameter)
: operation === 'solve-equal-lines'
? module.solveEqualLines(input.firstStartX, input.firstStartY, input.firstEndX, input.firstEndY, input.secondStartX, input.secondStartY, input.secondEndX, input.secondEndY)
: operation === 'solve-equal-circles'
? module.solveEqualCircles(input.firstCenterX, input.firstCenterY, input.firstRadius, input.secondCenterX, input.secondCenterY, input.secondRadius)
: operation === 'solve-tangent-circles'
? module.solveTangentCircles(input.firstCenterX, input.firstCenterY, input.firstRadius, input.secondCenterX, input.secondCenterY, input.secondRadius)
: operation === 'solve-point-symmetry'
? module.solvePointSymmetry(input.firstX, input.firstY, input.secondX, input.secondY, input.centerX, input.centerY)
: operation === 'solve-point-on-line'
? module.solvePointOnLine(input.pointX, input.pointY, input.startX, input.startY, input.endX, input.endY, input.vertical)
: operation === 'solve-point-on-circle'
? module.solvePointOnCircle(input.pointX, input.pointY, input.centerX, input.centerY, input.radius)
: operation === 'solve-point-on-arc'
? module.solvePointOnArc(input.pointX, input.pointY, input.centerX, input.centerY, input.radius, input.startAngle, input.endAngle)
: operation === 'solve-point-on-ellipse'
? module.solvePointOnEllipse(input.pointX, input.pointY, input.centerX, input.centerY, input.focusX, input.focusY, input.minorRadius)
: operation === 'solve-point-on-cubic-bspline'
? module.solvePointOnCubicBspline(input.pointX, input.pointY, input.pole0X, input.pole0Y, input.pole1X, input.pole1Y, input.pole2X, input.pole2Y, input.pole3X, input.pole3Y, input.pointParameter)
: operation === 'solve-cubic-bspline-weight'
? module.solveCubicBsplineWeight(input.pole0X, input.pole0Y, input.pole1X, input.pole1Y, input.pole2X, input.pole2Y, input.pole3X, input.pole3Y, input.weight0, input.weight1, input.weight2, input.weight3, input.controlPointIndex, input.targetWeight)
: operation === 'solve-parallel-lines'
? module.solveParallelLines(input.firstStartX, input.firstStartY, input.firstEndX, input.firstEndY, input.secondStartX, input.secondStartY, input.secondEndX, input.secondEndY, input.secondLength)
: operation === 'solve-coincident-lines'
? module.solveCoincidentLines(input.firstStartX, input.firstStartY, input.firstEndX, input.firstEndY, input.secondStartX, input.secondStartY, input.secondEndX, input.secondEndY, input.secondLength)
: operation === 'solve-coincident-line-points'
? module.solveCoincidentLinePoints(input.firstStartX, input.firstStartY, input.firstEndX, input.firstEndY, input.secondStartX, input.secondStartY, input.secondEndX, input.secondEndY, input.secondLength, input.firstEnd, input.secondEnd)
: operation === 'solve-snells-law-lines'
? module.solveSnellsLawLines(input.firstStartX, input.firstStartY, input.firstEndX, input.firstEndY, input.secondStartX, input.secondStartY, input.secondEndX, input.secondEndY, input.boundaryStartX, input.boundaryStartY, input.boundaryEndX, input.boundaryEndY, input.refractiveRatio, input.firstEnd, input.secondEnd)
: operation === 'solve-ellipse-internal-alignment'
? module.solveEllipseInternalAlignment(input.centerX, input.centerY, input.majorRadius, input.minorRadius, input.rotation, input.alignmentType)
: operation === 'solve-perpendicular-lines'
? module.solvePerpendicularLines(input.firstStartX, input.firstStartY, input.firstEndX, input.firstEndY, input.secondStartX, input.secondStartY, input.secondEndX, input.secondEndY, input.secondLength)
: module.solveHorizontalDistance(input.startX, input.startY, input.endX, input.endY, input.targetLength)
const values = Array.from({ length: result.size() }, (_, index) => result.get(index))
result.delete()
self.postMessage({ requestId, ok: true, values })
} catch (error) {
self.postMessage({ requestId, ok: false, error: error instanceof Error ? `${error.name}: ${error.message}` : String(error) })
}
})

18
public/sw.js Normal file
View File

@@ -0,0 +1,18 @@
const CACHE_NAME = 'bitbybit-cad-shell-v1'
const SHELL = ['/','/index.html','/manifest.webmanifest']
self.addEventListener('install', (event) => {
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL)).then(() => self.skipWaiting()))
})
self.addEventListener('activate', (event) => {
event.waitUntil(caches.keys().then((keys) => Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))).then(() => self.clients.claim()))
})
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET' || new URL(event.request.url).origin !== self.location.origin) return
event.respondWith(fetch(event.request).then((response) => {
if (response.ok) void caches.open(CACHE_NAME).then((cache) => cache.put(event.request, response.clone()))
return response
}).catch(() => caches.match(event.request).then((cached) => cached || caches.match('/index.html'))))
})

BIN
public/vendor/camotics/camotics-sweep.wasm vendored Executable file

Binary file not shown.

19
public/vendor/opencamlib/ocl.js vendored Executable file

File diff suppressed because one or more lines are too long