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

View File

@@ -0,0 +1,73 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {
CAMOTICS_SOURCE_REVISION,
OPEN_CAM_LIB_ARTIFACT_SHA256,
generateCamoticsSourceStageGcode,
nativeCamCapabilities,
runOpenCamLibDropCutter,
sampleFlatEndPath,
} from '../src/facade/camNativeSimulation'
test('native CAM capability report distinguishes workspace CAMotics CLI from browser execution', () => {
const capabilities = nativeCamCapabilities()
assert.equal(capabilities.openCamLib.backend, 'upstream-opencamlib-wasm')
assert.equal(capabilities.openCamLib.artifactSha256, OPEN_CAM_LIB_ARTIFACT_SHA256)
assert.deepEqual(capabilities.openCamLib.algorithms, ['path-drop-cutter', 'adaptive-path-drop-cutter', 'waterline', 'adaptive-waterline'])
assert.equal(capabilities.solidRemoval.backend, 'bitbybit-occt-wasm')
assert.equal(capabilities.solidRemoval.nativeSolid, true)
assert.equal(capabilities.solidRemoval.freeCadNativeEquivalent, false)
assert.deepEqual(capabilities.solidRemoval.sweepModes, ['sampled-flat-end', 'continuous-segment'])
assert.equal(capabilities.camotics.status, 'native-host-available')
assert.equal(capabilities.camotics.backend, 'camotics-native-host-qt5-tpl')
assert.equal(capabilities.camotics.workspaceNativeExecutable, true)
assert.equal(capabilities.camotics.nativeGuiPath, 'CAMotics/camotics')
assert.equal(capabilities.camotics.nativeTplPath, 'CAMotics/tplang')
assert.equal(capabilities.camotics.nativeArtifactEvidence, 'config/camotics-native-artifact.json')
assert.equal(capabilities.camotics.browserExecutable, false)
assert.equal(capabilities.camotics.browserWasmKernelExecutable, true)
assert.equal(capabilities.camotics.nativeCliPath, 'CAMotics/camsim')
assert.equal(capabilities.camotics.wasmKernel.backend, 'camotics-upstream-sweep-wasm')
assert.equal(capabilities.camotics.wasmKernel.fullCamoticsProgram, false)
assert.equal(capabilities.camotics.wasmKernel.gcodeParserIncluded, false)
assert.equal(capabilities.camotics.sourceRevision, CAMOTICS_SOURCE_REVISION)
assert.equal(capabilities.camotics.generationAuthority, 'camotics-source-stage')
assert.equal(capabilities.camotics.parserAuthority, 'linuxcnc-wasm')
assert.equal(capabilities.camotics.camoticsParsesCanonicalGcode, false)
assert.match(capabilities.camotics.reason, /workspace-verified/)
})
test('CAMotics source stage only generates canonical G-code for LinuxCNC WASM', () => {
const result = generateCamoticsSourceStageGcode([
{ type: 'rapid', point: [0, 0, 5] },
{ type: 'cut', point: [1.25, 2, 3.5] },
{ type: 'cut', point: [2, 2, 3.5] },
], { toolNumber: 2, feed: 120 })
assert.equal(result.backend, 'camotics-source-stage-contract')
assert.equal(result.nativeExecutable, false)
assert.equal(result.parserAuthority, 'linuxcnc-wasm')
assert.equal(result.camoticsParsesCanonicalGcode, false)
assert.equal(result.lineCount, 7)
assert.equal(result.gcode, 'G21G90\nM6 T2\nG0 X0. Y0. Z5.\nF120.\nG1 X1.25 Y2. Z3.5\nG1 X2.\nM2\n')
assert.throws(() => generateCamoticsSourceStageGcode([], { feed: 120 }), /must not be empty/)
assert.throws(() => generateCamoticsSourceStageGcode([{ type: 'cut', point: [0, 0, 0] }], { feed: 0 }), /greater than zero/)
})
test('flat-end solid-removal sampling has a deterministic chord bound', () => {
const samples = sampleFlatEndPath([[0, 0, 1], [3, 4, 1]], 1)
assert.equal(samples.length, 6)
assert.deepEqual(samples[0], [0, 0, 1])
assert.deepEqual(samples.at(-1), [3, 4, 1])
for (let index = 1; index < samples.length; index += 1) {
assert.ok(Math.hypot(...samples[index].map((value, axis) => value - samples[index - 1][axis])) <= 1 + 1e-12)
}
assert.deepEqual(sampleFlatEndPath([[1, 2, 3]], 0.5), [[1, 2, 3]])
assert.throws(() => sampleFlatEndPath([], 1), /must not be empty/)
assert.throws(() => sampleFlatEndPath([[0, 0, 0]], 0), /greater than zero/)
})
test('OpenCAMLib adapter validates unsafe input before requesting the browser WASM runtime', async () => {
await assert.rejects(() => runOpenCamLibDropCutter({ triangles: [], path: [[0, 0, 0], [1, 0, 0]], cutter: { shape: 'endmill', diameter: 1, length: 10 }, sampling: 0.1 }), /at least one surface triangle/)
await assert.rejects(() => runOpenCamLibDropCutter({ triangles: [[[0, 0, 0], [1, 0, 0], [0, 1, 0]]], path: [[0, 0, 0]], cutter: { shape: 'endmill', diameter: 1, length: 10 }, sampling: 0.1 }), /at least two points/)
await assert.rejects(() => runOpenCamLibDropCutter({ triangles: [[[0, 0, 0], [1, 0, 0], [0, 1, 0]]], path: [[0, 0, 0], [1, 0, 0]], cutter: { shape: 'endmill', diameter: 0, length: 10 }, sampling: 0.1 }), /greater than zero/)
})