feat: establish reproducible FreeCAD web compatibility baseline
This commit is contained in:
73
tests/camNativeSimulation.test.ts
Normal file
73
tests/camNativeSimulation.test.ts
Normal 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/)
|
||||
})
|
||||
Reference in New Issue
Block a user