113 lines
9.1 KiB
HTML
113 lines
9.1 KiB
HTML
<!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>
|