feat: establish reproducible FreeCAD web compatibility baseline
This commit is contained in:
89
public/chrome-sketcher-bspline-harness.html
Normal file
89
public/chrome-sketcher-bspline-harness.html
Normal 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>
|
||||
Reference in New Issue
Block a user