第 6 组:源码链接、构建和文档约束闭环完成
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
<div class="brand">CNC SIM</div>
|
||||
<div class="status-strip">
|
||||
<span id="wasmState" class="status alarm">WASM OFFLINE</span>
|
||||
<span id="storageState" class="status">OPFS INIT</span>
|
||||
<span id="modeState" class="status">MEM</span>
|
||||
<span id="unitState" class="status">MM</span>
|
||||
<span id="programState" class="status">RESET</span>
|
||||
@@ -65,7 +66,6 @@
|
||||
<span>BACKEND</span>
|
||||
<select id="backendSelect">
|
||||
<option value="linuxcnc-rs274">LINUXCNC</option>
|
||||
<option value="smoke">SMOKE</option>
|
||||
</select>
|
||||
</label>
|
||||
<span id="lineCount">0 LINES</span>
|
||||
|
||||
994
web/package-lock.json
generated
Normal file
994
web/package-lock.json
generated
Normal file
@@ -0,0 +1,994 @@
|
||||
{
|
||||
"name": "cnc-wasm-simulator-web",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cnc-wasm-simulator-web",
|
||||
"dependencies": {
|
||||
"three": "^0.164.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/three": "^0.164.0",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz",
|
||||
"integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz",
|
||||
"integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz",
|
||||
"integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz",
|
||||
"integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz",
|
||||
"integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz",
|
||||
"integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz",
|
||||
"integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz",
|
||||
"integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz",
|
||||
"integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz",
|
||||
"integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz",
|
||||
"integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz",
|
||||
"integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz",
|
||||
"integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz",
|
||||
"integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz",
|
||||
"integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz",
|
||||
"integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz",
|
||||
"integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz",
|
||||
"integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz",
|
||||
"integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz",
|
||||
"integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz",
|
||||
"integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz",
|
||||
"integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz",
|
||||
"integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz",
|
||||
"integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz",
|
||||
"integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@tweenjs/tween.js": {
|
||||
"version": "23.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
|
||||
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/stats.js": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
|
||||
"integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/three": {
|
||||
"version": "0.164.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.164.1.tgz",
|
||||
"integrity": "sha512-dR/trWDhyaNqJV38rl1TonlCA9DpnX7OPYDWD81bmBGn/+uEc3+zNalFxQcV4FlPTeDBhCY3SFWKvK6EJwL88g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@tweenjs/tween.js": "~23.1.1",
|
||||
"@types/stats.js": "*",
|
||||
"@types/webxr": "*",
|
||||
"fflate": "~0.8.2",
|
||||
"meshoptimizer": "~0.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/webxr": {
|
||||
"version": "0.5.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
||||
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.21.5",
|
||||
"@esbuild/android-arm": "0.21.5",
|
||||
"@esbuild/android-arm64": "0.21.5",
|
||||
"@esbuild/android-x64": "0.21.5",
|
||||
"@esbuild/darwin-arm64": "0.21.5",
|
||||
"@esbuild/darwin-x64": "0.21.5",
|
||||
"@esbuild/freebsd-arm64": "0.21.5",
|
||||
"@esbuild/freebsd-x64": "0.21.5",
|
||||
"@esbuild/linux-arm": "0.21.5",
|
||||
"@esbuild/linux-arm64": "0.21.5",
|
||||
"@esbuild/linux-ia32": "0.21.5",
|
||||
"@esbuild/linux-loong64": "0.21.5",
|
||||
"@esbuild/linux-mips64el": "0.21.5",
|
||||
"@esbuild/linux-ppc64": "0.21.5",
|
||||
"@esbuild/linux-riscv64": "0.21.5",
|
||||
"@esbuild/linux-s390x": "0.21.5",
|
||||
"@esbuild/linux-x64": "0.21.5",
|
||||
"@esbuild/netbsd-x64": "0.21.5",
|
||||
"@esbuild/openbsd-x64": "0.21.5",
|
||||
"@esbuild/sunos-x64": "0.21.5",
|
||||
"@esbuild/win32-arm64": "0.21.5",
|
||||
"@esbuild/win32-ia32": "0.21.5",
|
||||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
|
||||
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/meshoptimizer": {
|
||||
"version": "0.18.1",
|
||||
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz",
|
||||
"integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.12",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.60.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz",
|
||||
"integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.60.4",
|
||||
"@rollup/rollup-android-arm64": "4.60.4",
|
||||
"@rollup/rollup-darwin-arm64": "4.60.4",
|
||||
"@rollup/rollup-darwin-x64": "4.60.4",
|
||||
"@rollup/rollup-freebsd-arm64": "4.60.4",
|
||||
"@rollup/rollup-freebsd-x64": "4.60.4",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.60.4",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.60.4",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.60.4",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.60.4",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.60.4",
|
||||
"@rollup/rollup-linux-loong64-musl": "4.60.4",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.60.4",
|
||||
"@rollup/rollup-linux-ppc64-musl": "4.60.4",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.60.4",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.60.4",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.60.4",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.60.4",
|
||||
"@rollup/rollup-linux-x64-musl": "4.60.4",
|
||||
"@rollup/rollup-openbsd-x64": "4.60.4",
|
||||
"@rollup/rollup-openharmony-arm64": "4.60.4",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.60.4",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.60.4",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.60.4",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.60.4",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/three": {
|
||||
"version": "0.164.1",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.164.1.tgz",
|
||||
"integrity": "sha512-iC/hUBbl1vzFny7f5GtqzVXYjMJKaTPxiCxXfrvVdBi1Sf+jhd1CAkitiFwC7mIBFCo3MrDLJG97yisoaWig0w=="
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.21",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
"rollup": "^4.20.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^18.0.0 || >=20.0.0",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
"sass-embedded": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"lightningcss": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,66 +1,383 @@
|
||||
[
|
||||
{"field":"config","value":"configs/sim/axis/vismach/5axis/bridgemill/5axis.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/5axis/bridgemill/5axis.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/5axis/bridgemill/5axis.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/5axis/bridgemill/5axis.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/5axis/bridgemill/5axis.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/5axis/bridgemill/5axis.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/5axis/bridgemill/5axis.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/5axis/bridgemill/5axis.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/5axis/bridgemill/5axis.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"machine","value":"Sim-5Axis Bridge Mill (xyzbcw)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"kinematics","value":"5axiskins coordinates=xyzbcwy","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"bridgemill","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"5axis","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"Sim-5Axis Bridge Mill (xyzbcw)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"Sim-5Axis Bridge Mill","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"5axiskins coordinates=xyzbcwy","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"5axiskins","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halFile","value":"5axisgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halFile","value":"5axisgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halfile","value":"5axisgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halfile","value":"5axisgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"hal_file","value":"5axisgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"hal_file","value":"5axisgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"HALFILE","value":"5axisgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"HALFILE","value":"5axisgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"5axis_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"5axis_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"5axis_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"5axis_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"5axis_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"5axis_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"5axis_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"5axis_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"configs/sim/axis/vismach/5axis/bridgemill","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"bridgemill","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"machine","value":"sim-xyzab-tdr-kins (switchkins)","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"kinematics","value":"xyzab_tdr_kins","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"switchkins","value":"table-dual-rotary","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"switchkins","value":"xyzab-tdr","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"switchkins","value":"sim-xyzab-tdr-kins (switchkins)","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"switchkins","value":"sim-xyzab-tdr-kins","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"switchkins","value":"xyzab_tdr_kins","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"switchkins","value":"xyzab_tdr","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"switchkins","value":"xyzab-tdr-kins","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"postguiHalFile","value":"xyzab-tdr-postgui.hal","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"postguiHalFile","value":"xyzab-tdr-postgui","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"postgui_halfile","value":"xyzab-tdr-postgui.hal","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"postgui_halfile","value":"xyzab-tdr-postgui","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"postgui_hal_file","value":"xyzab-tdr-postgui.hal","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"postgui_hal_file","value":"xyzab-tdr-postgui","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"POSTGUI_HALFILE","value":"xyzab-tdr-postgui.hal","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"POSTGUI_HALFILE","value":"xyzab-tdr-postgui","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"remap","value":"configs/sim/axis/vismach/5axis/table-dual-rotary","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"remap","value":"table-dual-rotary","m428":1,"m429":0,"m430":-1},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"machine","value":"sim-xyzac-trt-kins (switchkins)","m428":1,"m429":0,"m430":2},
|
||||
{"field":"kinematics","value":"xyzac-trt-kins sparm=identityfirst","m428":1,"m429":0,"m430":2},
|
||||
{"field":"switchkins","value":"table-rotary-tilting","m428":1,"m429":0,"m430":2},
|
||||
{"field":"switchkins","value":"xyzac-trt","m428":1,"m429":0,"m430":2},
|
||||
{"field":"switchkins","value":"sim-xyzac-trt-kins (switchkins)","m428":1,"m429":0,"m430":2},
|
||||
{"field":"switchkins","value":"sim-xyzac-trt-kins","m428":1,"m429":0,"m430":2},
|
||||
{"field":"switchkins","value":"xyzac-trt-kins sparm=identityfirst","m428":1,"m429":0,"m430":2},
|
||||
{"field":"switchkins","value":"xyzac-trt-kins","m428":1,"m429":0,"m430":2},
|
||||
{"field":"postguiHalFile","value":"switchkins_postgui.hal","m428":1,"m429":0,"m430":2},
|
||||
{"field":"postguiHalFile","value":"switchkins_postgui","m428":1,"m429":0,"m430":2},
|
||||
{"field":"postgui_halfile","value":"switchkins_postgui.hal","m428":1,"m429":0,"m430":2},
|
||||
{"field":"postgui_halfile","value":"switchkins_postgui","m428":1,"m429":0,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"switchkins_postgui.hal","m428":1,"m429":0,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"switchkins_postgui","m428":1,"m429":0,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"switchkins_postgui.hal","m428":1,"m429":0,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"switchkins_postgui","m428":1,"m429":0,"m430":2},
|
||||
{"field":"remap","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting","m428":1,"m429":0,"m430":2},
|
||||
{"field":"remap","value":"table-rotary-tilting","m428":1,"m429":0,"m430":2},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini","m428":1,"m429":0,"m430":2},
|
||||
{"field":"machine","value":"sim-xyzbc-trt-kins (switchkins)","m428":1,"m429":0,"m430":2},
|
||||
{"field":"kinematics","value":"xyzbc-trt-kins sparm=identityfirst","m428":1,"m429":0,"m430":2},
|
||||
{"field":"switchkins","value":"xyzbc-trt","m428":1,"m429":0,"m430":2},
|
||||
{"field":"switchkins","value":"sim-xyzbc-trt-kins (switchkins)","m428":1,"m429":0,"m430":2},
|
||||
{"field":"switchkins","value":"sim-xyzbc-trt-kins","m428":1,"m429":0,"m430":2},
|
||||
{"field":"switchkins","value":"xyzbc-trt-kins sparm=identityfirst","m428":1,"m429":0,"m430":2},
|
||||
{"field":"switchkins","value":"xyzbc-trt-kins","m428":1,"m429":0,"m430":2},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"machine","value":"xyzacb-trsrn (switchkins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"kinematics","value":"xyzacb_trsrn","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"xyzacb-trsrn_twp","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"xyzacb-trsrn","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"xyzacb-trsrn (switchkins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"xyzacb_trsrn","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"xyzacb-trsrn_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"xyzacb-trsrn_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"xyzacb-trsrn_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"xyzacb-trsrn_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"xyzacb-trsrn_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"xyzacb-trsrn_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"xyzacb-trsrn_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"xyzacb-trsrn_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"table-rotary_spindle-rotary-nutating","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"machine","value":"xyzbca-trsrn (switchkins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"kinematics","value":"xyzbca_trsrn","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"xyzbca-trsrn_twp","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"xyzbca-trsrn","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"xyzbca-trsrn (switchkins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"xyzbca_trsrn","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"xyzbca-trsrn_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"xyzbca-trsrn_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"xyzbca-trsrn_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"xyzbca-trsrn_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"xyzbca-trsrn_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"xyzbca-trsrn_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"xyzbca-trsrn_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"xyzbca-trsrn_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/hexapod-sim/hexapod.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/hexapod-sim/hexapod.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/hexapod-sim/hexapod.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/hexapod-sim/hexapod.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/hexapod-sim/hexapod.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/hexapod-sim/hexapod.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/hexapod-sim/hexapod.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/hexapod-sim/hexapod.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/hexapod-sim/hexapod.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"machine","value":"hexapod (switchkins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"kinematics","value":"genhexkins","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"hexapod-sim","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"hexapod","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"hexapod (switchkins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"genhexkins","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halFile","value":"kinematics.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halFile","value":"kinematics","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halfile","value":"kinematics.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halfile","value":"kinematics","m428":0,"m429":1,"m430":2},
|
||||
{"field":"hal_file","value":"kinematics.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"hal_file","value":"kinematics","m428":0,"m429":1,"m430":2},
|
||||
{"field":"HALFILE","value":"kinematics.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"HALFILE","value":"kinematics","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"hexapod_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"hexapod_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"hexapod_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"hexapod_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"hexapod_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"hexapod_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"hexapod_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"hexapod_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"configs/sim/axis/vismach/hexapod-sim","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"hexapod-sim","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/melfa-sim/melfa.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/melfa-sim/melfa.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/melfa-sim/melfa.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/melfa-sim/melfa.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/melfa-sim/melfa.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/melfa-sim/melfa.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/melfa-sim/melfa.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/melfa-sim/melfa.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/melfa-sim/melfa.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"machine","value":"melfa (mm)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"kinematics","value":"genserkins","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"melfa-sim","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"melfa","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"melfa (mm)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"genserkins","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halFile","value":"melfa_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halFile","value":"melfa_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halfile","value":"melfa_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halfile","value":"melfa_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"hal_file","value":"melfa_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"hal_file","value":"melfa_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"HALFILE","value":"melfa_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"HALFILE","value":"melfa_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"melfa-postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"melfa-postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"melfa-postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"melfa-postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"melfa-postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"melfa-postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"melfa-postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"melfa-postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"configs/sim/axis/vismach/melfa-sim","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"melfa-sim","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/millturn/millturn.ini","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/millturn/millturn.ini","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/millturn/millturn.ini","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/millturn/millturn.ini","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/millturn/millturn.ini","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/millturn/millturn.ini","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/millturn/millturn.ini","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/millturn/millturn.ini","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/millturn/millturn.ini","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"machine","value":"millturn (mm)","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"kinematics","value":"millturn","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"switchkins","value":"millturn","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"switchkins","value":"millturn (mm)","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"halFile","value":"millturn.hal","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"halFile","value":"millturn","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"halfile","value":"millturn.hal","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"halfile","value":"millturn","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"hal_file","value":"millturn.hal","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"hal_file","value":"millturn","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"HALFILE","value":"millturn.hal","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"HALFILE","value":"millturn","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"postguiHalFile","value":"millturn-postgui.hal","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"postguiHalFile","value":"millturn-postgui","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"postgui_halfile","value":"millturn-postgui.hal","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"postgui_halfile","value":"millturn-postgui","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"postgui_hal_file","value":"millturn-postgui.hal","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"postgui_hal_file","value":"millturn-postgui","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"POSTGUI_HALFILE","value":"millturn-postgui.hal","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"POSTGUI_HALFILE","value":"millturn-postgui","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"remap","value":"configs/sim/axis/vismach/millturn","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"remap","value":"millturn","m428":0,"m429":1,"m430":-1},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/puma/puma.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/puma/puma.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/puma/puma.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/puma/puma.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/puma/puma.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/puma/puma.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/puma/puma.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/puma/puma.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/puma/puma.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"machine","value":"PUMA (pumakins,switchkins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"kinematics","value":"pumakins","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"puma","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"PUMA (pumakins,switchkins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"PUMA","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"pumakins","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halFile","value":"puma_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halFile","value":"puma_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halfile","value":"puma_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halfile","value":"puma_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"hal_file","value":"puma_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"hal_file","value":"puma_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"HALFILE","value":"puma_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"HALFILE","value":"puma_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"puma_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"puma_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"puma_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"puma_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"puma_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"puma_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"puma_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"puma_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"configs/sim/axis/vismach/puma","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"puma","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/puma/puma_cube.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/puma/puma_cube.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/puma/puma_cube.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/puma/puma_cube.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/puma/puma_cube.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/puma/puma_cube.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/puma/puma_cube.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/puma/puma_cube.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/puma/puma_cube.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"machine","value":"puma_cube.ini (pumakins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"puma_cube","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"puma_cube.ini (pumakins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"puma_cube.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/puma/puma560.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/puma/puma560.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/puma/puma560.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/puma/puma560.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/puma/puma560.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/puma/puma560.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/puma/puma560.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/puma/puma560.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/puma/puma560.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"machine","value":"puma560 (switchkins) (inch)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"puma560","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"puma560 (switchkins) (inch)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halFile","value":"puma560_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halFile","value":"puma560_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halfile","value":"puma560_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"halfile","value":"puma560_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"hal_file","value":"puma560_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"hal_file","value":"puma560_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"HALFILE","value":"puma560_dh.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"HALFILE","value":"puma560_dh","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"puma560_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"puma560_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"puma560_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"puma560_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"puma560_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"puma560_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"puma560_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"puma560_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/puma/puma560_uvw.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/puma/puma560_uvw.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/puma/puma560_uvw.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/puma/puma560_uvw.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/puma/puma560_uvw.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/puma/puma560_uvw.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/puma/puma560_uvw.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/puma/puma560_uvw.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/puma/puma560_uvw.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"puma560_uvw","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/axis/vismach/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/axis/vismach/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/axis/vismach/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/axis/vismach/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/axis/vismach/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/axis/vismach/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/axis/vismach/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/axis/vismach/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/axis/vismach/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"machine","value":"SCARA (genserkins,switchkins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"kinematics","value":"scarakins coordinates=xyzcab","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"scara","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"SCARA (genserkins,switchkins)","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"SCARA","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"scarakins coordinates=xyzcab","m428":0,"m429":1,"m430":2},
|
||||
{"field":"switchkins","value":"scarakins","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"scara_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postguiHalFile","value":"scara_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"scara_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_halfile","value":"scara_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"scara_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"postgui_hal_file","value":"scara_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"scara_postgui.hal","m428":0,"m429":1,"m430":2},
|
||||
{"field":"POSTGUI_HALFILE","value":"scara_postgui","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"configs/sim/axis/vismach/scara","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"scara","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/qtaxis/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/qtvcp_screens/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2}
|
||||
{"field":"configPath","value":"configs/sim/qtaxis/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/qtaxis/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/qtaxis/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/qtaxis/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/qtaxis/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/qtaxis/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/qtaxis/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/qtaxis/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"configs/sim/qtaxis/non-trivial/scara","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config","value":"configs/sim/qtvcp_screens/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"configPath","value":"configs/sim/qtvcp_screens/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"config_path","value":"configs/sim/qtvcp_screens/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini","value":"configs/sim/qtvcp_screens/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFile","value":"configs/sim/qtvcp_screens/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"iniFileName","value":"configs/sim/qtvcp_screens/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file","value":"configs/sim/qtvcp_screens/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"ini_file_name","value":"configs/sim/qtvcp_screens/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"INI_FILE_NAME","value":"configs/sim/qtvcp_screens/non-trivial/scara/scara.ini","m428":0,"m429":1,"m430":2},
|
||||
{"field":"remap","value":"configs/sim/qtvcp_screens/non-trivial/scara","m428":0,"m429":1,"m430":2}
|
||||
]
|
||||
|
||||
225
web/src/app.js
225
web/src/app.js
@@ -2,6 +2,7 @@ import { createWasmSimulator } from "./wasm-core.js";
|
||||
|
||||
const elements = {
|
||||
wasmState: document.querySelector("#wasmState"),
|
||||
storageState: document.querySelector("#storageState"),
|
||||
modeState: document.querySelector("#modeState"),
|
||||
unitState: document.querySelector("#unitState"),
|
||||
programState: document.querySelector("#programState"),
|
||||
@@ -26,12 +27,82 @@ const elements = {
|
||||
|
||||
let simulatorPromise = null;
|
||||
let lastEvents = [];
|
||||
let isParsing = false;
|
||||
let hasUserEditedProgram = false;
|
||||
let saveProgramTimer = 0;
|
||||
let saveProgramSequence = 0;
|
||||
let saveProgramPromise = Promise.resolve();
|
||||
let startupPromise = Promise.resolve();
|
||||
const programPath = "programs/current.ngc";
|
||||
// LinuxCNC source basis: interp_internal.hh defines RS274NGC_PARAMETER_FILE_NAME_DEFAULT.
|
||||
const linuxCncParameterFileName = "rs274ngc.var";
|
||||
const parameterPath = `parameters/${linuxCncParameterFileName}`;
|
||||
const linuxCncBackend = "linuxcnc-rs274";
|
||||
const opfsOptions = { required: true, mountPoint: "/cnc", workspacePath: "cnc-simulator" };
|
||||
|
||||
function setStatus(node, text, alarm = false) {
|
||||
node.textContent = text;
|
||||
node.classList.toggle("alarm", alarm);
|
||||
}
|
||||
|
||||
function setRuntimeReady(simulator) {
|
||||
setStatus(elements.wasmState, "WASM ONLINE");
|
||||
if (simulator.fs?.opfs) {
|
||||
const storageState = elements.storageState.textContent;
|
||||
if (storageState === "OPFS SAVING" || storageState.startsWith("OPFS SAVED")) {
|
||||
setStatus(elements.storageState, storageState);
|
||||
} else {
|
||||
setStatus(elements.storageState, "OPFS READY");
|
||||
}
|
||||
} else {
|
||||
setStatus(elements.storageState, "OPFS OFF", true);
|
||||
}
|
||||
}
|
||||
|
||||
function setRuntimeUnavailable() {
|
||||
setStatus(elements.wasmState, "WASM OFFLINE", true);
|
||||
setStatus(elements.storageState, "OPFS OFF", true);
|
||||
}
|
||||
|
||||
function setControlsEnabled(enabled) {
|
||||
elements.parseBtn.disabled = !enabled;
|
||||
elements.resetBtn.disabled = !enabled;
|
||||
elements.holdBtn.disabled = !enabled;
|
||||
elements.stopBtn.disabled = !enabled;
|
||||
elements.backendSelect.disabled = !enabled;
|
||||
}
|
||||
|
||||
function setStorageSaved(size) {
|
||||
setStatus(elements.storageState, Number.isFinite(size) ? `OPFS SAVED ${size}B` : "OPFS SAVED");
|
||||
}
|
||||
|
||||
function setProgramReady() {
|
||||
if (elements.programState.textContent !== "ALARM") {
|
||||
setStatus(elements.programState, "READY");
|
||||
}
|
||||
}
|
||||
|
||||
async function statIfExists(opfs, path) {
|
||||
if (!(await opfs.exists(path))) {
|
||||
return null;
|
||||
}
|
||||
return opfs.stat(path);
|
||||
}
|
||||
|
||||
async function logWorkspaceState(opfs) {
|
||||
const programStat = await statIfExists(opfs, programPath);
|
||||
if (programStat?.kind === "file") {
|
||||
log(`OPFS PROGRAM ${programStat.size}B`);
|
||||
}
|
||||
|
||||
const parameterStat = await statIfExists(opfs, parameterPath);
|
||||
if (parameterStat?.kind === "file") {
|
||||
log(`OPFS PARAMETERS ${parameterStat.size}B`);
|
||||
} else {
|
||||
log("OPFS PARAMETERS PENDING");
|
||||
}
|
||||
}
|
||||
|
||||
function log(message, error = false) {
|
||||
const line = document.createElement("div");
|
||||
line.className = `alarm-line${error ? " error" : ""}`;
|
||||
@@ -163,21 +234,63 @@ function updateLineCount() {
|
||||
elements.lineCount.textContent = `${count} LINES`;
|
||||
}
|
||||
|
||||
async function getOpfsWorkspace() {
|
||||
const simulator = await getSimulator();
|
||||
setRuntimeReady(simulator);
|
||||
if (!simulator.fs.opfs) {
|
||||
throw new Error("OPFS workspace is required for browser program parsing");
|
||||
}
|
||||
return simulator.fs.opfs;
|
||||
}
|
||||
|
||||
async function getSimulator() {
|
||||
if (!simulatorPromise) {
|
||||
simulatorPromise = createWasmSimulator();
|
||||
simulatorPromise = createWasmSimulator({
|
||||
opfs: opfsOptions,
|
||||
}).then((simulator) => {
|
||||
setRuntimeReady(simulator);
|
||||
return simulator;
|
||||
}).catch((error) => {
|
||||
simulatorPromise = null;
|
||||
globalThis.__cncSimulatorForTest = null;
|
||||
setRuntimeUnavailable();
|
||||
throw error;
|
||||
});
|
||||
globalThis.__cncSimulatorForTest = simulatorPromise;
|
||||
}
|
||||
return simulatorPromise;
|
||||
}
|
||||
|
||||
async function parseProgram() {
|
||||
if (isParsing) {
|
||||
return;
|
||||
}
|
||||
isParsing = true;
|
||||
setControlsEnabled(false);
|
||||
setStatus(elements.programState, "RUN");
|
||||
let runtimeReady = false;
|
||||
let savedProgramSize = null;
|
||||
try {
|
||||
await startupPromise.catch(() => {});
|
||||
const simulator = await getSimulator();
|
||||
setStatus(elements.wasmState, "WASM ONLINE");
|
||||
const events = simulator.parse(elements.input.value, "linuxcnc", {
|
||||
backend: elements.backendSelect.value,
|
||||
});
|
||||
runtimeReady = true;
|
||||
setRuntimeReady(simulator);
|
||||
const parseOptions = {
|
||||
backend: linuxCncBackend,
|
||||
};
|
||||
if (!simulator.fs.opfs) {
|
||||
throw new Error("OPFS workspace is required for browser program parsing");
|
||||
}
|
||||
elements.backendSelect.value = linuxCncBackend;
|
||||
await saveProgramPromise.catch(() => {});
|
||||
await simulator.fs.opfs.writeFile(programPath, elements.input.value);
|
||||
const programStat = await simulator.fs.opfs.stat(programPath);
|
||||
savedProgramSize = programStat.kind === "file" ? programStat.size : null;
|
||||
setStorageSaved(savedProgramSize);
|
||||
const events = await simulator.parseFileWithParameterFile(programPath, parameterPath, "linuxcnc", parseOptions);
|
||||
const parameterStat = await simulator.fs.opfs.stat(parameterPath);
|
||||
setStorageSaved(parameterStat.size);
|
||||
log(`OPFS PARAMETERS ${parameterStat.size}B`);
|
||||
lastEvents = events;
|
||||
drawToolpath(events);
|
||||
|
||||
@@ -193,9 +306,82 @@ async function parseProgram() {
|
||||
setStatus(elements.programState, "END");
|
||||
log(`OK ${events.length} EVENTS`);
|
||||
} catch (error) {
|
||||
setStatus(elements.wasmState, "WASM OFFLINE", true);
|
||||
if (!runtimeReady) {
|
||||
setRuntimeUnavailable();
|
||||
}
|
||||
if (savedProgramSize !== null) {
|
||||
log(`OPFS PROGRAM SAVED ${savedProgramSize}B`);
|
||||
}
|
||||
setStatus(elements.programState, "ALARM", true);
|
||||
log(error.message, true);
|
||||
} finally {
|
||||
isParsing = false;
|
||||
setControlsEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveProgramDraft() {
|
||||
const sequence = ++saveProgramSequence;
|
||||
saveProgramPromise = saveProgramPromise.catch(() => {}).then(async () => {
|
||||
const opfs = await getOpfsWorkspace();
|
||||
const program = elements.input.value;
|
||||
setStatus(elements.storageState, "OPFS SAVING");
|
||||
await opfs.writeFile(programPath, program);
|
||||
if (sequence === saveProgramSequence) {
|
||||
const stat = await opfs.stat(programPath);
|
||||
setStorageSaved(stat.size);
|
||||
setProgramReady();
|
||||
}
|
||||
});
|
||||
try {
|
||||
await saveProgramPromise;
|
||||
} catch (error) {
|
||||
setStatus(elements.storageState, "OPFS ALARM", true);
|
||||
log(error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleProgramSave() {
|
||||
window.clearTimeout(saveProgramTimer);
|
||||
saveProgramTimer = window.setTimeout(() => {
|
||||
void saveProgramDraft();
|
||||
}, 350);
|
||||
}
|
||||
|
||||
function flushProgramSave() {
|
||||
window.clearTimeout(saveProgramTimer);
|
||||
if (hasUserEditedProgram) {
|
||||
void saveProgramDraft();
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreProgram() {
|
||||
try {
|
||||
const opfs = await getOpfsWorkspace();
|
||||
if (!(await opfs.exists(programPath))) {
|
||||
await opfs.writeFile(programPath, elements.input.value);
|
||||
const stat = await opfs.stat(programPath);
|
||||
setStorageSaved(stat.size);
|
||||
setProgramReady();
|
||||
log(`OPFS PROGRAM CREATED ${stat.size}B`);
|
||||
await logWorkspaceState(opfs);
|
||||
return;
|
||||
}
|
||||
const program = new TextDecoder().decode(await opfs.readFile(programPath));
|
||||
if (hasUserEditedProgram) {
|
||||
return;
|
||||
}
|
||||
elements.input.value = program;
|
||||
updateLineCount();
|
||||
const stat = await opfs.stat(programPath);
|
||||
setStorageSaved(stat.size);
|
||||
setProgramReady();
|
||||
log("OPFS PROGRAM RESTORED");
|
||||
await logWorkspaceState(opfs);
|
||||
} catch (error) {
|
||||
if (error?.name !== "NotFoundError") {
|
||||
log(error.message, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +391,7 @@ document.querySelectorAll("[data-mode]").forEach((button) => {
|
||||
});
|
||||
});
|
||||
|
||||
elements.backendSelect.value = linuxCncBackend;
|
||||
elements.parseBtn.addEventListener("click", parseProgram);
|
||||
elements.resetBtn.addEventListener("click", () => {
|
||||
lastEvents = [];
|
||||
@@ -214,8 +401,32 @@ elements.resetBtn.addEventListener("click", () => {
|
||||
});
|
||||
elements.holdBtn.addEventListener("click", () => setStatus(elements.programState, "HOLD"));
|
||||
elements.stopBtn.addEventListener("click", () => setStatus(elements.programState, "STOP", true));
|
||||
elements.input.addEventListener("input", updateLineCount);
|
||||
elements.input.addEventListener("input", () => {
|
||||
updateLineCount();
|
||||
hasUserEditedProgram = true;
|
||||
setStatus(elements.programState, "EDIT");
|
||||
scheduleProgramSave();
|
||||
});
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (!(event.ctrlKey || event.metaKey) || event.altKey) {
|
||||
return;
|
||||
}
|
||||
if (event.key === "s") {
|
||||
event.preventDefault();
|
||||
void saveProgramDraft();
|
||||
} else if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
void parseProgram();
|
||||
}
|
||||
});
|
||||
window.addEventListener("resize", resizeCanvas);
|
||||
window.addEventListener("pagehide", flushProgramSave);
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.visibilityState === "hidden") {
|
||||
flushProgramSave();
|
||||
}
|
||||
});
|
||||
|
||||
updateLineCount();
|
||||
resizeCanvas();
|
||||
startupPromise = restoreProgram();
|
||||
|
||||
@@ -84,8 +84,19 @@ export type CncParseOptions = {
|
||||
config?: string;
|
||||
configPath?: string;
|
||||
config_path?: string;
|
||||
ini?: string;
|
||||
iniFile?: string;
|
||||
iniFileName?: string;
|
||||
ini_file?: string;
|
||||
ini_file_name?: string;
|
||||
INI_FILE_NAME?: string;
|
||||
halFile?: string;
|
||||
halfile?: string;
|
||||
hal_file?: string;
|
||||
postguiHalFile?: string;
|
||||
postgui_halfile?: string;
|
||||
postgui_hal_file?: string;
|
||||
POSTGUI_HALFILE?: string;
|
||||
kinematics?: string;
|
||||
trt?: "xyzbc" | "xyzac" | string;
|
||||
pivotLength?: number;
|
||||
|
||||
52
web/src/wasm-core.d.ts
vendored
52
web/src/wasm-core.d.ts
vendored
@@ -2,13 +2,65 @@ import type { CncDialect, CncEvent, CncParseOptions } from "./index";
|
||||
|
||||
export type WasmSimulator = {
|
||||
parse(program: string, dialect?: CncDialect, options?: CncParseOptions): CncEvent[];
|
||||
parseFile(path: string, dialect?: CncDialect, options?: CncParseOptions): Promise<CncEvent[]>;
|
||||
parseWithParameterFile(
|
||||
program: string,
|
||||
parameterPath: string,
|
||||
dialect?: CncDialect,
|
||||
options?: CncParseOptions,
|
||||
): Promise<CncEvent[]>;
|
||||
parseFileWithParameterFile(
|
||||
path: string,
|
||||
parameterPath: string,
|
||||
dialect?: CncDialect,
|
||||
options?: CncParseOptions,
|
||||
): Promise<CncEvent[]>;
|
||||
dispose(): void;
|
||||
fs: {
|
||||
opfs: WasmOpfsWorkspace | null;
|
||||
module: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export type WasmOpfsWorkspace = {
|
||||
mountPoint: string;
|
||||
workspacePath: string;
|
||||
resolvePath(path: string): string;
|
||||
readFile(path: string): Promise<Uint8Array>;
|
||||
readDirectory(path: string): Promise<string[]>;
|
||||
persistFile(path: string): Promise<Uint8Array>;
|
||||
persistDirectory(path: string): Promise<string[]>;
|
||||
writeFile(path: string, data: Uint8Array | string): Promise<void>;
|
||||
copyFile(fromPath: string, toPath: string): Promise<Uint8Array>;
|
||||
moveFile(fromPath: string, toPath: string): Promise<void>;
|
||||
exists(path: string): Promise<boolean>;
|
||||
stat(path: string): Promise<WasmOpfsEntryStat>;
|
||||
loadParameterFile(path: string): Promise<Uint8Array | null>;
|
||||
persistParameterFile(path: string): Promise<Uint8Array>;
|
||||
removeFile(path: string): Promise<void>;
|
||||
removeDirectory(path: string): Promise<void>;
|
||||
clear(): Promise<void>;
|
||||
};
|
||||
|
||||
export type WasmOpfsEntryStat =
|
||||
| {
|
||||
kind: "file";
|
||||
size: number;
|
||||
}
|
||||
| {
|
||||
kind: "directory";
|
||||
size: null;
|
||||
};
|
||||
|
||||
export type WasmModuleOptions = {
|
||||
locateFile?: (file: string) => string;
|
||||
print?: (text: string) => void;
|
||||
printErr?: (text: string) => void;
|
||||
opfs?: false | {
|
||||
required?: boolean;
|
||||
mountPoint?: string;
|
||||
workspacePath?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export function createWasmSimulator(moduleOptions?: WasmModuleOptions): Promise<WasmSimulator>;
|
||||
|
||||
@@ -41,6 +41,486 @@ const EVENT_OFFSETS = {
|
||||
reserved: 268,
|
||||
};
|
||||
|
||||
const DEFAULT_OPFS_MOUNT_POINT = "/cnc";
|
||||
const LINUXCNC_DEFAULT_PARAMETER_FILE = "rs274ngc.var";
|
||||
const LINUXCNC_PARAMETER_SCRATCH_FILES = [
|
||||
LINUXCNC_DEFAULT_PARAMETER_FILE,
|
||||
`${LINUXCNC_DEFAULT_PARAMETER_FILE}.new`,
|
||||
`${LINUXCNC_DEFAULT_PARAMETER_FILE}.bak`,
|
||||
];
|
||||
|
||||
function isOpfsAvailable() {
|
||||
return Boolean(globalThis.navigator?.storage?.getDirectory);
|
||||
}
|
||||
|
||||
function assertRelativePath(path) {
|
||||
if (!path || typeof path !== "string") {
|
||||
throw new Error("OPFS path must be a non-empty relative path");
|
||||
}
|
||||
if (
|
||||
path.startsWith("/") ||
|
||||
path.includes("\\") ||
|
||||
path.includes("\0") ||
|
||||
path.split("/").some((part) => part === "" || part === "." || part === "..")
|
||||
) {
|
||||
throw new Error(`OPFS path must stay inside the CNC workspace: ${path}`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertWorkspacePath(path) {
|
||||
assertRelativePath(path);
|
||||
}
|
||||
|
||||
function assertMountPoint(path) {
|
||||
if (!path || typeof path !== "string") {
|
||||
throw new Error("OPFS mount point must be a non-empty absolute path");
|
||||
}
|
||||
const parts = path.split("/").slice(1);
|
||||
if (
|
||||
!path.startsWith("/") ||
|
||||
path.includes("\\") ||
|
||||
path.includes("\0") ||
|
||||
parts.some((part) => part === "" || part === "." || part === "..")
|
||||
) {
|
||||
throw new Error(`OPFS mount point must stay inside the Emscripten filesystem: ${path}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function getOpfsDirectoryHandle(path, create) {
|
||||
if (!isOpfsAvailable()) {
|
||||
throw new Error("OPFS is not available in this browser context");
|
||||
}
|
||||
let directory = await globalThis.navigator.storage.getDirectory();
|
||||
for (const part of path.split("/").filter(Boolean)) {
|
||||
directory = await directory.getDirectoryHandle(part, { create });
|
||||
}
|
||||
return directory;
|
||||
}
|
||||
|
||||
async function readOpfsFile(workspacePath, relativePath) {
|
||||
assertRelativePath(relativePath);
|
||||
const parts = relativePath.split("/");
|
||||
const fileName = parts.pop();
|
||||
const directory = await getOpfsDirectoryHandle([workspacePath, ...parts].filter(Boolean).join("/"), false);
|
||||
const file = await directory.getFileHandle(fileName, { create: false });
|
||||
return new Uint8Array(await (await file.getFile()).arrayBuffer());
|
||||
}
|
||||
|
||||
async function writeOpfsFile(workspacePath, relativePath, data) {
|
||||
assertRelativePath(relativePath);
|
||||
const parts = relativePath.split("/");
|
||||
const fileName = parts.pop();
|
||||
const directory = await getOpfsDirectoryHandle([workspacePath, ...parts].filter(Boolean).join("/"), true);
|
||||
const file = await directory.getFileHandle(fileName, { create: true });
|
||||
const writable = await file.createWritable();
|
||||
try {
|
||||
await writable.truncate(0);
|
||||
await writable.write(data);
|
||||
await writable.close();
|
||||
} catch (error) {
|
||||
try {
|
||||
await writable.abort();
|
||||
} catch (_abortError) {
|
||||
// Preserve the original OPFS write failure.
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function removeOpfsEntry(workspacePath, relativePath, recursive) {
|
||||
assertRelativePath(relativePath);
|
||||
const parts = relativePath.split("/");
|
||||
const entryName = parts.pop();
|
||||
try {
|
||||
const directory = await getOpfsDirectoryHandle([workspacePath, ...parts].filter(Boolean).join("/"), false);
|
||||
await directory.removeEntry(entryName, { recursive });
|
||||
} catch (error) {
|
||||
if (error?.name !== "NotFoundError") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function opfsEntryExists(workspacePath, relativePath) {
|
||||
assertRelativePath(relativePath);
|
||||
const parts = relativePath.split("/");
|
||||
const entryName = parts.pop();
|
||||
try {
|
||||
const directory = await getOpfsDirectoryHandle([workspacePath, ...parts].filter(Boolean).join("/"), false);
|
||||
try {
|
||||
await directory.getFileHandle(entryName, { create: false });
|
||||
return true;
|
||||
} catch (fileError) {
|
||||
if (fileError?.name !== "NotFoundError" && fileError?.name !== "TypeMismatchError") {
|
||||
throw fileError;
|
||||
}
|
||||
}
|
||||
try {
|
||||
await directory.getDirectoryHandle(entryName, { create: false });
|
||||
return true;
|
||||
} catch (directoryError) {
|
||||
if (directoryError?.name !== "NotFoundError" && directoryError?.name !== "TypeMismatchError") {
|
||||
throw directoryError;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error?.name === "NotFoundError") {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function statOpfsEntry(workspacePath, relativePath) {
|
||||
assertRelativePath(relativePath);
|
||||
const parts = relativePath.split("/");
|
||||
const entryName = parts.pop();
|
||||
const directory = await getOpfsDirectoryHandle([workspacePath, ...parts].filter(Boolean).join("/"), false);
|
||||
try {
|
||||
const file = await directory.getFileHandle(entryName, { create: false });
|
||||
return { kind: "file", size: (await file.getFile()).size };
|
||||
} catch (fileError) {
|
||||
if (fileError?.name !== "TypeMismatchError") {
|
||||
throw fileError;
|
||||
}
|
||||
}
|
||||
await directory.getDirectoryHandle(entryName, { create: false });
|
||||
return { kind: "directory", size: null };
|
||||
}
|
||||
|
||||
async function clearOpfsWorkspace(workspacePath) {
|
||||
const parts = workspacePath.split("/").filter(Boolean);
|
||||
if (parts.length === 0) {
|
||||
return;
|
||||
}
|
||||
const entryName = parts.pop();
|
||||
const directory = await getOpfsDirectoryHandle(parts.join("/"), true);
|
||||
try {
|
||||
await directory.removeEntry(entryName, { recursive: true });
|
||||
} catch (error) {
|
||||
if (error?.name !== "NotFoundError") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
await getOpfsDirectoryHandle(workspacePath, true);
|
||||
}
|
||||
|
||||
async function replaceOpfsDirectory(workspacePath, relativePath) {
|
||||
assertRelativePath(relativePath);
|
||||
await removeOpfsEntry(workspacePath, relativePath, true);
|
||||
const directory = await getOpfsDirectoryHandle([workspacePath, relativePath].filter(Boolean).join("/"), true);
|
||||
for await (const [name] of directory.entries()) {
|
||||
await directory.removeEntry(name, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function readOpfsDirectoryTree(workspacePath, relativePath, relativeRoot = "") {
|
||||
assertRelativePath(relativePath);
|
||||
const directory = await getOpfsDirectoryHandle([workspacePath, relativePath, relativeRoot].filter(Boolean).join("/"), false);
|
||||
const files = [];
|
||||
const directories = [];
|
||||
for await (const [name, handle] of directory.entries()) {
|
||||
const path = relativeRoot ? `${relativeRoot}/${name}` : name;
|
||||
if (handle.kind === "directory") {
|
||||
directories.push(path);
|
||||
const child = await readOpfsDirectoryTree(workspacePath, relativePath, path);
|
||||
directories.push(...child.directories);
|
||||
files.push(...child.files);
|
||||
} else if (handle.kind === "file") {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
return {
|
||||
directories: directories.sort(),
|
||||
files: files.sort(),
|
||||
};
|
||||
}
|
||||
|
||||
function ensureWasmDirectory(module, path) {
|
||||
const parts = path.split("/").filter(Boolean);
|
||||
let current = "";
|
||||
for (const part of parts) {
|
||||
current += `/${part}`;
|
||||
try {
|
||||
module.FS.mkdir(current);
|
||||
} catch (error) {
|
||||
if (error?.errno !== 20) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readWasmDirectoryTree(module, rootPath, relativeRoot = "") {
|
||||
const files = [];
|
||||
const directories = [];
|
||||
for (const entry of module.FS.readdir(rootPath)) {
|
||||
if (entry === "." || entry === "..") {
|
||||
continue;
|
||||
}
|
||||
const path = `${rootPath}/${entry}`;
|
||||
const relativePath = relativeRoot ? `${relativeRoot}/${entry}` : entry;
|
||||
const mode = module.FS.stat(path).mode;
|
||||
if (module.FS.isDir(mode)) {
|
||||
directories.push(relativePath);
|
||||
const child = readWasmDirectoryTree(module, path, relativePath);
|
||||
directories.push(...child.directories);
|
||||
files.push(...child.files);
|
||||
} else if (module.FS.isFile(mode)) {
|
||||
files.push(relativePath);
|
||||
}
|
||||
}
|
||||
return {
|
||||
directories: directories.sort(),
|
||||
files: files.sort(),
|
||||
};
|
||||
}
|
||||
|
||||
function removeWasmPath(module, path, recursive) {
|
||||
let mode;
|
||||
try {
|
||||
mode = module.FS.stat(path).mode;
|
||||
} catch (error) {
|
||||
if (error?.errno !== 44) {
|
||||
throw error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (module.FS.isDir(mode)) {
|
||||
if (!recursive) {
|
||||
module.FS.rmdir(path);
|
||||
return;
|
||||
}
|
||||
for (const entry of module.FS.readdir(path)) {
|
||||
if (entry !== "." && entry !== "..") {
|
||||
removeWasmPath(module, `${path}/${entry}`, true);
|
||||
}
|
||||
}
|
||||
module.FS.rmdir(path);
|
||||
} else {
|
||||
module.FS.unlink(path);
|
||||
}
|
||||
}
|
||||
|
||||
function wasmPathExists(module, path) {
|
||||
try {
|
||||
module.FS.stat(path);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error?.errno !== 44) {
|
||||
throw error;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function readFirstExistingWasmFile(module, paths) {
|
||||
for (const path of paths) {
|
||||
try {
|
||||
return module.FS.readFile(path);
|
||||
} catch (error) {
|
||||
if (error?.errno !== 44) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function writeWasmFileReplacingPath(module, path, data) {
|
||||
removeWasmPath(module, path, true);
|
||||
const parentIndex = path.lastIndexOf("/");
|
||||
if (parentIndex > 0) {
|
||||
ensureWasmDirectory(module, path.slice(0, parentIndex));
|
||||
}
|
||||
module.FS.writeFile(path, data);
|
||||
}
|
||||
|
||||
function cleanupLinuxCncParameterFiles(module) {
|
||||
for (const path of LINUXCNC_PARAMETER_SCRATCH_FILES) {
|
||||
removeWasmPath(module, path, true);
|
||||
removeWasmPath(module, `/${path}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
function installOpfsWorkspace(module, options) {
|
||||
const opfsOptions = options.opfs ?? (isOpfsAvailable() ? {} : false);
|
||||
if (opfsOptions === false) {
|
||||
if (isOpfsAvailable()) {
|
||||
throw new Error("OPFS cannot be disabled in browser contexts with OPFS support");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (!module.FS) {
|
||||
throw new Error("cnc_sim.js was not built with Emscripten FS export");
|
||||
}
|
||||
if (!isOpfsAvailable()) {
|
||||
if (opfsOptions.required) {
|
||||
throw new Error("OPFS is only available in browser contexts");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const mountPoint = opfsOptions.mountPoint ?? DEFAULT_OPFS_MOUNT_POINT;
|
||||
const workspacePath = opfsOptions.workspacePath ?? "cnc-simulator";
|
||||
assertMountPoint(mountPoint);
|
||||
assertWorkspacePath(workspacePath);
|
||||
ensureWasmDirectory(module, mountPoint);
|
||||
return {
|
||||
mountPoint,
|
||||
workspacePath,
|
||||
resolvePath(path) {
|
||||
assertRelativePath(path);
|
||||
return `${mountPoint}/${path}`;
|
||||
},
|
||||
async readFile(path) {
|
||||
const data = await readOpfsFile(workspacePath, path);
|
||||
const wasmPath = this.resolvePath(path);
|
||||
writeWasmFileReplacingPath(module, wasmPath, data);
|
||||
return data;
|
||||
},
|
||||
async readDirectory(path) {
|
||||
assertRelativePath(path);
|
||||
let tree;
|
||||
try {
|
||||
tree = await readOpfsDirectoryTree(workspacePath, path);
|
||||
} catch (error) {
|
||||
if (error?.name === "NotFoundError") {
|
||||
removeWasmPath(module, this.resolvePath(path), true);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
const wasmPath = this.resolvePath(path);
|
||||
removeWasmPath(module, wasmPath, true);
|
||||
ensureWasmDirectory(module, wasmPath);
|
||||
for (const directory of tree.directories) {
|
||||
ensureWasmDirectory(module, `${wasmPath}/${directory}`);
|
||||
}
|
||||
for (const file of tree.files) {
|
||||
await this.readFile(`${path}/${file}`);
|
||||
}
|
||||
return tree.files;
|
||||
},
|
||||
async persistFile(path) {
|
||||
const wasmPath = this.resolvePath(path);
|
||||
const data = module.FS.readFile(wasmPath);
|
||||
await writeOpfsFile(workspacePath, path, data);
|
||||
return data;
|
||||
},
|
||||
async persistDirectory(path) {
|
||||
assertRelativePath(path);
|
||||
const wasmPath = this.resolvePath(path);
|
||||
const tree = readWasmDirectoryTree(module, wasmPath);
|
||||
await replaceOpfsDirectory(workspacePath, path);
|
||||
for (const directory of tree.directories) {
|
||||
await getOpfsDirectoryHandle([workspacePath, path, directory].filter(Boolean).join("/"), true);
|
||||
}
|
||||
for (const file of tree.files) {
|
||||
await this.persistFile(`${path}/${file}`);
|
||||
}
|
||||
return tree.files;
|
||||
},
|
||||
async writeFile(path, data) {
|
||||
await writeOpfsFile(workspacePath, path, data);
|
||||
const wasmPath = this.resolvePath(path);
|
||||
writeWasmFileReplacingPath(module, wasmPath, data);
|
||||
},
|
||||
// LinuxCNC source basis: rs274ngc_pre.cc save_parameters() links the
|
||||
// current parameter file to filename + ".bak" before replacing it.
|
||||
async copyFile(fromPath, toPath) {
|
||||
assertRelativePath(fromPath);
|
||||
assertRelativePath(toPath);
|
||||
const data = await readOpfsFile(workspacePath, fromPath);
|
||||
await writeOpfsFile(workspacePath, toPath, data);
|
||||
|
||||
const fromWasmPath = this.resolvePath(fromPath);
|
||||
const toWasmPath = this.resolvePath(toPath);
|
||||
writeWasmFileReplacingPath(module, fromWasmPath, data);
|
||||
writeWasmFileReplacingPath(module, toWasmPath, data);
|
||||
return data;
|
||||
},
|
||||
// LinuxCNC source basis: rs274ngc_pre.cc save_parameters() writes a
|
||||
// temporary filename + ".new" and renames it over the parameter file.
|
||||
async moveFile(fromPath, toPath) {
|
||||
assertRelativePath(fromPath);
|
||||
assertRelativePath(toPath);
|
||||
if (fromPath === toPath) {
|
||||
return;
|
||||
}
|
||||
const data = await readOpfsFile(workspacePath, fromPath);
|
||||
await writeOpfsFile(workspacePath, toPath, data);
|
||||
await removeOpfsEntry(workspacePath, fromPath, false);
|
||||
|
||||
const fromWasmPath = this.resolvePath(fromPath);
|
||||
const toWasmPath = this.resolvePath(toPath);
|
||||
writeWasmFileReplacingPath(module, toWasmPath, data);
|
||||
removeWasmPath(module, fromWasmPath, false);
|
||||
},
|
||||
async exists(path) {
|
||||
return opfsEntryExists(workspacePath, path);
|
||||
},
|
||||
async stat(path) {
|
||||
return statOpfsEntry(workspacePath, path);
|
||||
},
|
||||
// LinuxCNC source basis: rs274ngc_pre.cc restore_parameters() reads the
|
||||
// configured parameter file, and save_parameters() writes filename + ".new"
|
||||
// before replacing the main file and managing filename + ".bak".
|
||||
async loadParameterFile(path) {
|
||||
assertRelativePath(path);
|
||||
try {
|
||||
const data = await readOpfsFile(workspacePath, path);
|
||||
writeWasmFileReplacingPath(module, LINUXCNC_DEFAULT_PARAMETER_FILE, data);
|
||||
writeWasmFileReplacingPath(module, `/${LINUXCNC_DEFAULT_PARAMETER_FILE}`, data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
if (error?.name !== "NotFoundError") {
|
||||
throw error;
|
||||
}
|
||||
cleanupLinuxCncParameterFiles(module);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async persistParameterFile(path) {
|
||||
assertRelativePath(path);
|
||||
const data = readFirstExistingWasmFile(module, [
|
||||
LINUXCNC_DEFAULT_PARAMETER_FILE,
|
||||
`/${LINUXCNC_DEFAULT_PARAMETER_FILE}`,
|
||||
]);
|
||||
if (data === null) {
|
||||
throw new Error("LinuxCNC parameter file was not produced by the interpreter");
|
||||
}
|
||||
await writeOpfsFile(workspacePath, path, data);
|
||||
await removeOpfsEntry(workspacePath, `${path}.new`, false);
|
||||
removeWasmPath(module, `${LINUXCNC_DEFAULT_PARAMETER_FILE}.new`, false);
|
||||
removeWasmPath(module, `/${LINUXCNC_DEFAULT_PARAMETER_FILE}.new`, false);
|
||||
const backupPath = `${LINUXCNC_DEFAULT_PARAMETER_FILE}.bak`;
|
||||
const backupData = readFirstExistingWasmFile(module, [backupPath, `/${backupPath}`]);
|
||||
if (backupData !== null) {
|
||||
await writeOpfsFile(workspacePath, `${path}.bak`, backupData);
|
||||
} else {
|
||||
await removeOpfsEntry(workspacePath, `${path}.bak`, false);
|
||||
}
|
||||
return data;
|
||||
},
|
||||
async removeFile(path) {
|
||||
await removeOpfsEntry(workspacePath, path, false);
|
||||
removeWasmPath(module, this.resolvePath(path), false);
|
||||
},
|
||||
async removeDirectory(path) {
|
||||
await removeOpfsEntry(workspacePath, path, true);
|
||||
removeWasmPath(module, this.resolvePath(path), true);
|
||||
},
|
||||
async clear() {
|
||||
await clearOpfsWorkspace(workspacePath);
|
||||
for (const entry of module.FS.readdir(mountPoint)) {
|
||||
if (entry !== "." && entry !== "..") {
|
||||
removeWasmPath(module, `${mountPoint}/${entry}`, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function readPose(view, offset) {
|
||||
return {
|
||||
x: view.getFloat64(offset + 0, true),
|
||||
@@ -82,12 +562,21 @@ async function loadModuleFactory() {
|
||||
}
|
||||
|
||||
export async function createWasmSimulator(moduleOptions = {}) {
|
||||
if (moduleOptions.opfs === false && isOpfsAvailable()) {
|
||||
throw new Error("OPFS cannot be disabled in browser contexts with OPFS support");
|
||||
}
|
||||
const hasOpfsOptions = Object.prototype.hasOwnProperty.call(moduleOptions, "opfs");
|
||||
const emscriptenModuleOptions = hasOpfsOptions
|
||||
? (({ opfs: _opfs, ...rest }) => rest)(moduleOptions)
|
||||
: moduleOptions;
|
||||
|
||||
const createModule = await loadModuleFactory();
|
||||
if (!createModule) {
|
||||
throw new Error("cnc_sim.js did not export createCncSimModule");
|
||||
}
|
||||
|
||||
const module = await createModule(moduleOptions);
|
||||
const module = hasOpfsOptions ? await createModule(emscriptenModuleOptions) : await createModule(moduleOptions);
|
||||
const opfs = installOpfsWorkspace(module, moduleOptions);
|
||||
const create = module.cwrap("cnc_sim_create", "number", []);
|
||||
const destroy = module.cwrap("cnc_sim_destroy", null, ["number"]);
|
||||
const reset = module.cwrap("cnc_sim_reset", null, ["number"]);
|
||||
@@ -100,9 +589,9 @@ export async function createWasmSimulator(moduleOptions = {}) {
|
||||
const handle = create();
|
||||
let callbackPtr = 0;
|
||||
|
||||
return {
|
||||
parse(program, dialect = "linuxcnc", options = {}) {
|
||||
const events = [];
|
||||
const parseText = (program, dialect = "linuxcnc", options = {}, parseOptions = {}) => {
|
||||
const events = [];
|
||||
try {
|
||||
reset(handle);
|
||||
setDialect(handle, DIALECT[dialect] ?? DIALECT.linuxcnc);
|
||||
|
||||
@@ -117,8 +606,19 @@ export async function createWasmSimulator(moduleOptions = {}) {
|
||||
...(options.config ? { config: options.config } : {}),
|
||||
...(options.configPath ? { configPath: options.configPath } : {}),
|
||||
...(options.config_path ? { config_path: options.config_path } : {}),
|
||||
...(options.ini ? { ini: options.ini } : {}),
|
||||
...(options.iniFile ? { iniFile: options.iniFile } : {}),
|
||||
...(options.iniFileName ? { iniFileName: options.iniFileName } : {}),
|
||||
...(options.ini_file ? { ini_file: options.ini_file } : {}),
|
||||
...(options.ini_file_name ? { ini_file_name: options.ini_file_name } : {}),
|
||||
...(options.INI_FILE_NAME ? { INI_FILE_NAME: options.INI_FILE_NAME } : {}),
|
||||
...(options.halFile ? { halFile: options.halFile } : {}),
|
||||
...(options.halfile ? { halfile: options.halfile } : {}),
|
||||
...(options.hal_file ? { hal_file: options.hal_file } : {}),
|
||||
...(options.postguiHalFile ? { postguiHalFile: options.postguiHalFile } : {}),
|
||||
...(options.postgui_halfile ? { postgui_halfile: options.postgui_halfile } : {}),
|
||||
...(options.postgui_hal_file ? { postgui_hal_file: options.postgui_hal_file } : {}),
|
||||
...(options.POSTGUI_HALFILE ? { POSTGUI_HALFILE: options.POSTGUI_HALFILE } : {}),
|
||||
...(options.kinematics ? { kinematics: options.kinematics } : {}),
|
||||
...(options.trt ? { trt: options.trt } : {}),
|
||||
...(options.pivotLength !== undefined ? { pivotLength: options.pivotLength } : {}),
|
||||
@@ -135,9 +635,13 @@ export async function createWasmSimulator(moduleOptions = {}) {
|
||||
});
|
||||
const configBytes = module.lengthBytesUTF8(config) + 1;
|
||||
const configPtr = module._malloc(configBytes);
|
||||
module.stringToUTF8(config, configPtr, configBytes);
|
||||
const configRc = loadConfig(handle, configPtr, configBytes - 1);
|
||||
module._free(configPtr);
|
||||
let configRc;
|
||||
try {
|
||||
module.stringToUTF8(config, configPtr, configBytes);
|
||||
configRc = loadConfig(handle, configPtr, configBytes - 1);
|
||||
} finally {
|
||||
module._free(configPtr);
|
||||
}
|
||||
if (configRc !== 0) {
|
||||
throw new Error(module.UTF8ToString(lastError(handle)));
|
||||
}
|
||||
@@ -153,14 +657,59 @@ export async function createWasmSimulator(moduleOptions = {}) {
|
||||
|
||||
const bytes = module.lengthBytesUTF8(program) + 1;
|
||||
const ptr = module._malloc(bytes);
|
||||
module.stringToUTF8(program, ptr, bytes);
|
||||
const rc = parseProgram(handle, ptr, bytes - 1);
|
||||
module._free(ptr);
|
||||
let rc;
|
||||
try {
|
||||
module.stringToUTF8(program, ptr, bytes);
|
||||
rc = parseProgram(handle, ptr, bytes - 1);
|
||||
} finally {
|
||||
module._free(ptr);
|
||||
}
|
||||
|
||||
if (rc !== 0) {
|
||||
throw new Error(module.UTF8ToString(lastError(handle)));
|
||||
}
|
||||
return events;
|
||||
} finally {
|
||||
if (opfs && !parseOptions.keepLinuxCncParameterFiles) {
|
||||
cleanupLinuxCncParameterFiles(module);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
parse(program, dialect = "linuxcnc", options = {}) {
|
||||
return parseText(program, dialect, options);
|
||||
},
|
||||
|
||||
async parseFile(path, dialect = "linuxcnc", options = {}) {
|
||||
if (!opfs) {
|
||||
throw new Error("OPFS workspace is not available for program file parsing");
|
||||
}
|
||||
const program = new TextDecoder().decode(await opfs.readFile(path));
|
||||
return parseText(program, dialect, options);
|
||||
},
|
||||
|
||||
async parseWithParameterFile(program, parameterPath, dialect = "linuxcnc", options = {}) {
|
||||
if (!opfs) {
|
||||
throw new Error("OPFS workspace is not available for parameter file persistence");
|
||||
}
|
||||
cleanupLinuxCncParameterFiles(module);
|
||||
await opfs.loadParameterFile(parameterPath);
|
||||
try {
|
||||
const events = parseText(program, dialect, options, { keepLinuxCncParameterFiles: true });
|
||||
await opfs.persistParameterFile(parameterPath);
|
||||
return events;
|
||||
} finally {
|
||||
cleanupLinuxCncParameterFiles(module);
|
||||
}
|
||||
},
|
||||
|
||||
async parseFileWithParameterFile(path, parameterPath, dialect = "linuxcnc", options = {}) {
|
||||
if (!opfs) {
|
||||
throw new Error("OPFS workspace is not available for program and parameter file parsing");
|
||||
}
|
||||
const program = new TextDecoder().decode(await opfs.readFile(path));
|
||||
return this.parseWithParameterFile(program, parameterPath, dialect, options);
|
||||
},
|
||||
|
||||
dispose() {
|
||||
@@ -170,5 +719,10 @@ export async function createWasmSimulator(moduleOptions = {}) {
|
||||
}
|
||||
destroy(handle);
|
||||
},
|
||||
|
||||
fs: {
|
||||
opfs,
|
||||
module,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
66
web/test-browser-wasm-smoke-app-sections.js
Normal file
66
web/test-browser-wasm-smoke-app-sections.js
Normal file
@@ -0,0 +1,66 @@
|
||||
export async function runBrowserAppOpfsSections(context) {
|
||||
const {
|
||||
browserHasOpfs,
|
||||
createHiddenAppFrame,
|
||||
disposeAppFrame,
|
||||
expectIncludes,
|
||||
expectText,
|
||||
getAppFrameSimulator,
|
||||
parseAppFrameProgram,
|
||||
runSection,
|
||||
waitFor,
|
||||
waitForAppAlarm,
|
||||
waitForAppControls,
|
||||
} = context;
|
||||
if (!browserHasOpfs()) {
|
||||
return;
|
||||
}
|
||||
await runSection("browser app opfs save", async () => {
|
||||
const appFrame = await createHiddenAppFrame();
|
||||
try {
|
||||
await waitForAppControls(appFrame, "app frame did not load CNC controls");
|
||||
parseAppFrameProgram(appFrame, "G21 G90\nG0 X1\nG28.1\nM30\n");
|
||||
await waitForAppAlarm(appFrame, "OK ", "app frame did not parse program");
|
||||
const appSimulator = await getAppFrameSimulator(appFrame, "app frame did not create OPFS simulator workspace");
|
||||
const savedProgram = new TextDecoder().decode(await appSimulator.fs.opfs.readFile("programs/current.ngc"));
|
||||
expectText(savedProgram, "G21 G90\nG0 X1\nG28.1\nM30\n",
|
||||
"app frame did not persist current program into OPFS");
|
||||
const savedParameterFile = new TextDecoder().decode(await appSimulator.fs.opfs.readFile("parameters/rs274ngc.var"));
|
||||
expectIncludes(savedParameterFile, "5161\t1.000000",
|
||||
"app frame did not persist LinuxCNC parameter state into OPFS");
|
||||
} finally {
|
||||
await disposeAppFrame(appFrame);
|
||||
}
|
||||
});
|
||||
await runSection("browser app opfs restore", async () => {
|
||||
const restoredAppFrame = await createHiddenAppFrame();
|
||||
try {
|
||||
await waitForAppControls(restoredAppFrame, "restored app frame did not load CNC controls");
|
||||
await waitFor(() => {
|
||||
const restoredInput = restoredAppFrame.contentDocument?.querySelector("#programInput");
|
||||
return restoredInput?.value === "G21 G90\nG0 X1\nG28.1\nM30\n";
|
||||
}, "app frame did not restore current program from OPFS after reload");
|
||||
await waitForAppAlarm(
|
||||
restoredAppFrame,
|
||||
"OPFS PROGRAM RESTORED",
|
||||
"app frame did not report OPFS program restore after reload",
|
||||
);
|
||||
await getAppFrameSimulator(restoredAppFrame, "restored app frame did not create OPFS simulator workspace");
|
||||
parseAppFrameProgram(
|
||||
restoredAppFrame,
|
||||
"G21 G90\nF100\nO10 if [#5161 EQ 1]\nG1 X12\nO10 endif\nM30\n",
|
||||
);
|
||||
await waitForAppAlarm(
|
||||
restoredAppFrame,
|
||||
"OK ",
|
||||
"restored app frame did not parse program with reloaded parameter state",
|
||||
);
|
||||
await waitFor(
|
||||
() => restoredAppFrame.contentDocument?.querySelector("#axisX")?.value === "12.000",
|
||||
"app frame did not reload LinuxCNC parameter state from OPFS after reload",
|
||||
);
|
||||
} finally {
|
||||
await disposeAppFrame(restoredAppFrame);
|
||||
}
|
||||
});
|
||||
}
|
||||
176
web/test-browser-wasm-smoke-helpers.js
Normal file
176
web/test-browser-wasm-smoke-helpers.js
Normal file
@@ -0,0 +1,176 @@
|
||||
const textDecoder = new TextDecoder();
|
||||
const textEncoder = new TextEncoder();
|
||||
const linuxCncParameterScratchFiles = ["rs274ngc.var", "rs274ngc.var.new", "rs274ngc.var.bak"];
|
||||
|
||||
export function createBrowserSmokeHarness(result) {
|
||||
const sectionResults = [];
|
||||
const near = (actual, expected) => Math.abs(actual - expected) < 1e-6;
|
||||
const waitFor = async (predicate, message) => {
|
||||
for (let i = 0; i < 50; i += 1) {
|
||||
if (predicate()) {
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
throw new Error(message);
|
||||
};
|
||||
const runSection = async (name, action) => {
|
||||
const sectionNumber = sectionResults.length + 1;
|
||||
result.textContent = `running: ${sectionNumber}: ${name}`;
|
||||
try {
|
||||
await action();
|
||||
sectionResults.push(name);
|
||||
} catch (error) {
|
||||
const detail = error && error.stack ? error.stack : error;
|
||||
throw new Error(`section "${name}" failed: ${detail}`);
|
||||
}
|
||||
};
|
||||
const completedMessage = () => `browser wasm smoke passed (${sectionResults.length} sections)`;
|
||||
const browserHasOpfs = () => navigator.storage && navigator.storage.getDirectory;
|
||||
const expectEvent = (events, predicate, message) => {
|
||||
if (!events.some(predicate)) {
|
||||
throw new Error(message);
|
||||
}
|
||||
};
|
||||
const expectNoEvent = (events, predicate, message) => {
|
||||
if (events.some(predicate)) {
|
||||
throw new Error(message);
|
||||
}
|
||||
};
|
||||
const expectText = (actual, expected, message) => {
|
||||
if (actual !== expected) {
|
||||
throw new Error(message);
|
||||
}
|
||||
};
|
||||
const expectIncludes = (actual, expected, message) => {
|
||||
if (!actual.includes(expected)) {
|
||||
throw new Error(message);
|
||||
}
|
||||
};
|
||||
const expectFileList = (actual, expected, message) => {
|
||||
if (
|
||||
actual.length !== expected.length ||
|
||||
expected.some((entry) => !actual.includes(entry))
|
||||
) {
|
||||
throw new Error(`${message}: ${actual.join(",")}`);
|
||||
}
|
||||
};
|
||||
const expectErrorContaining = async (action, expectedText, message) => {
|
||||
try {
|
||||
await action();
|
||||
throw new Error(message);
|
||||
} catch (error) {
|
||||
if (!String(error?.message ?? error).includes(expectedText)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
const expectMissingOpfsFile = async (action, message) => {
|
||||
try {
|
||||
await action();
|
||||
throw new Error(message);
|
||||
} catch (error) {
|
||||
if (error?.name !== "NotFoundError") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
const expectMissingWasmPath = (action, message) => {
|
||||
try {
|
||||
action();
|
||||
throw new Error(message);
|
||||
} catch (error) {
|
||||
if (error?.errno !== 44) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
const expectWasmFilesMissing = (fs, paths, messageForPath) => {
|
||||
for (const wasmPath of paths) {
|
||||
expectMissingWasmPath(
|
||||
() => fs.readFile(wasmPath),
|
||||
messageForPath(wasmPath),
|
||||
);
|
||||
}
|
||||
};
|
||||
const decodeBytes = (bytes) => textDecoder.decode(bytes);
|
||||
const encodeText = (text) => textEncoder.encode(text);
|
||||
const readOpfsText = async (simulator, path) => decodeBytes(await simulator.fs.opfs.readFile(path));
|
||||
const readWasmText = (simulator, path) => decodeBytes(simulator.fs.module.FS.readFile(path));
|
||||
const writeWasmText = (simulator, path, text) => simulator.fs.module.FS.writeFile(path, encodeText(text));
|
||||
const removeWasmPathIfExists = (simulator, path) => {
|
||||
try {
|
||||
simulator.fs.module.FS.unlink(path);
|
||||
} catch (error) {
|
||||
if (error?.errno !== 44) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
const expectOpfsText = async (simulator, path, expected, message) => {
|
||||
expectText(await readOpfsText(simulator, path), expected, message);
|
||||
};
|
||||
const expectWasmText = (simulator, path, expected, message) => {
|
||||
expectText(readWasmText(simulator, path), expected, message);
|
||||
};
|
||||
const expectOpfsExists = async (simulator, path, message) => {
|
||||
if (!(await simulator.fs.opfs.exists(path))) {
|
||||
throw new Error(message);
|
||||
}
|
||||
};
|
||||
const expectOpfsMissing = async (simulator, path, message) => {
|
||||
if (await simulator.fs.opfs.exists(path)) {
|
||||
throw new Error(message);
|
||||
}
|
||||
};
|
||||
const expectOpfsFileStat = async (simulator, path, expectedSize, message) => {
|
||||
const stat = await simulator.fs.opfs.stat(path);
|
||||
if (stat.kind !== "file" || stat.size !== expectedSize) {
|
||||
throw new Error(message);
|
||||
}
|
||||
};
|
||||
const expectOpfsDirectoryStat = async (simulator, path, message) => {
|
||||
const stat = await simulator.fs.opfs.stat(path);
|
||||
if (stat.kind !== "directory" || stat.size !== null) {
|
||||
throw new Error(message);
|
||||
}
|
||||
};
|
||||
const expectLinuxCncParameterScratchMissing = (simulator, messageForPath) => {
|
||||
for (const parameterPath of linuxCncParameterScratchFiles) {
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.stat(parameterPath),
|
||||
messageForPath(parameterPath),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
browserHasOpfs,
|
||||
completedMessage,
|
||||
decodeBytes,
|
||||
encodeText,
|
||||
expectErrorContaining,
|
||||
expectEvent,
|
||||
expectFileList,
|
||||
expectIncludes,
|
||||
expectLinuxCncParameterScratchMissing,
|
||||
expectMissingOpfsFile,
|
||||
expectMissingWasmPath,
|
||||
expectNoEvent,
|
||||
expectOpfsDirectoryStat,
|
||||
expectOpfsExists,
|
||||
expectOpfsFileStat,
|
||||
expectOpfsMissing,
|
||||
expectOpfsText,
|
||||
expectText,
|
||||
expectWasmFilesMissing,
|
||||
expectWasmText,
|
||||
near,
|
||||
readOpfsText,
|
||||
readWasmText,
|
||||
removeWasmPathIfExists,
|
||||
runSection,
|
||||
waitFor,
|
||||
writeWasmText,
|
||||
};
|
||||
}
|
||||
72
web/test-browser-wasm-smoke-linuxcnc-sections.js
Normal file
72
web/test-browser-wasm-smoke-linuxcnc-sections.js
Normal file
@@ -0,0 +1,72 @@
|
||||
export async function runLinuxCncBrowserSections(context, simulator) {
|
||||
const {
|
||||
expectEvent,
|
||||
expectMissingWasmPath,
|
||||
near,
|
||||
runSection,
|
||||
} = context;
|
||||
await runSection("linuxcnc browser basic parse", async () => {
|
||||
const events = simulator.parse("G21 G90\nG0 X0\nG1 X5 F100\nM30\n", "linuxcnc", {
|
||||
backend: "linuxcnc-rs274",
|
||||
});
|
||||
expectEvent(events, (event) => event.type === "linear-feed" && near(event.end.x, 5),
|
||||
"missing expected LinuxCNC WASM browser linear-feed event");
|
||||
expectEvent(events, (event) => event.type === "program-end",
|
||||
"missing expected LinuxCNC WASM browser program-end event");
|
||||
});
|
||||
let configCases = [];
|
||||
await runSection("linuxcnc generated switchkins cases load", async () => {
|
||||
const configCaseResponse = await fetch("/public/linuxcnc_switchkins_remap_config_cases.json");
|
||||
if (!configCaseResponse.ok) {
|
||||
throw new Error(`failed to load LinuxCNC switchkins config cases: ${configCaseResponse.status}`);
|
||||
}
|
||||
configCases = await configCaseResponse.json();
|
||||
if (!Array.isArray(configCases) || configCases.length === 0) {
|
||||
throw new Error("missing LinuxCNC switchkins config cases");
|
||||
}
|
||||
});
|
||||
for (const configCase of configCases) {
|
||||
await runSection(`linuxcnc generated switchkins ${configCase.field}=${configCase.value}`, async () => {
|
||||
const options = {
|
||||
backend: "linuxcnc-rs274",
|
||||
[configCase.field]: configCase.value,
|
||||
};
|
||||
const program = configCase.m430 >= 0 ? "M428\nM429\nM430\n" : "M428\nM429\n";
|
||||
const configEvents = simulator.parse(program, "linuxcnc", options);
|
||||
for (const [line, kinstype] of [
|
||||
[1, configCase.m428],
|
||||
[2, configCase.m429],
|
||||
...(configCase.m430 >= 0 ? [[3, configCase.m430]] : []),
|
||||
]) {
|
||||
expectEvent(configEvents,
|
||||
(event) => event.type === "kinematics-switch" && event.line === line && event.reserved === kinstype,
|
||||
`missing LinuxCNC WASM browser generated ${configCase.field}=${configCase.value} line ${line} switchkins type ${kinstype}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
await runSection("linuxcnc browser explicit scara switchkins", async () => {
|
||||
const switchEvents = simulator.parse("M428\nM429\nM430\n", "linuxcnc", {
|
||||
backend: "linuxcnc-rs274",
|
||||
switchkins: "scara",
|
||||
});
|
||||
for (const [line, kinstype] of [
|
||||
[1, 0],
|
||||
[2, 1],
|
||||
[3, 2],
|
||||
]) {
|
||||
expectEvent(switchEvents,
|
||||
(event) => event.type === "kinematics-switch" && event.line === line && event.reserved === kinstype,
|
||||
`missing LinuxCNC WASM browser M${427 + line} switchkins type ${kinstype}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
await runSection("linuxcnc browser parse parameter scratch cleanup", async () => {
|
||||
for (const parameterPath of ["rs274ngc.var", "rs274ngc.var.new", "rs274ngc.var.bak"]) {
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.stat(parameterPath),
|
||||
`browser parse left LinuxCNC parameter state outside OPFS: ${parameterPath}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
114
web/test-browser-wasm-smoke-opfs-basic-sections.js
Normal file
114
web/test-browser-wasm-smoke-opfs-basic-sections.js
Normal file
@@ -0,0 +1,114 @@
|
||||
export async function runBrowserOpfsBasicSections(context, simulator) {
|
||||
const {
|
||||
expectErrorContaining,
|
||||
expectEvent,
|
||||
expectMissingWasmPath,
|
||||
expectText,
|
||||
near,
|
||||
runSection,
|
||||
withSmokeSimulator,
|
||||
} = context;
|
||||
|
||||
await runSection("opfs workspace clear and healthcheck", async () => {
|
||||
if (!simulator.fs.opfs) {
|
||||
throw new Error("expected OPFS workspace to be available in browser smoke");
|
||||
}
|
||||
await simulator.fs.opfs.clear();
|
||||
if (simulator.fs.opfs.workspacePath !== "cnc-simulator-smoke") {
|
||||
throw new Error("unexpected OPFS workspace path");
|
||||
}
|
||||
await simulator.fs.opfs.writeFile("healthcheck.ngc", "G21 G90\n");
|
||||
if (!(await simulator.fs.opfs.exists("healthcheck.ngc"))) {
|
||||
throw new Error("OPFS exists did not find a persisted file");
|
||||
}
|
||||
if (await simulator.fs.opfs.exists("missing-healthcheck.ngc")) {
|
||||
throw new Error("OPFS exists reported a missing file");
|
||||
}
|
||||
const healthcheckStat = await simulator.fs.opfs.stat("healthcheck.ngc");
|
||||
if (healthcheckStat.kind !== "file" || healthcheckStat.size !== 8) {
|
||||
throw new Error("OPFS stat did not report the persisted healthcheck file size");
|
||||
}
|
||||
const clearCheck = new TextDecoder().decode(await simulator.fs.opfs.readFile("healthcheck.ngc"));
|
||||
expectText(clearCheck, "G21 G90\n", "OPFS workspace did not survive clear/write round trip");
|
||||
});
|
||||
|
||||
await runSection("opfs default mount check", async () => {
|
||||
await withSmokeSimulator((defaultMountSimulator) => {
|
||||
if (!defaultMountSimulator.fs.opfs || defaultMountSimulator.fs.opfs.mountPoint !== "/cnc") {
|
||||
throw new Error("default OPFS mount point was not preserved");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await runSection("opfs program write and read", async () => {
|
||||
const encoded = new TextEncoder().encode("G21 G90\nG0 X1\n");
|
||||
await simulator.fs.opfs.writeFile("programs/browser-smoke.ngc", encoded);
|
||||
const decoded = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("programs/browser-smoke.ngc"),
|
||||
);
|
||||
expectText(decoded, "G21 G90\nG0 X1\n", "OPFS round trip did not preserve browser smoke program");
|
||||
});
|
||||
|
||||
await runSection("opfs unsafe path rejection", async () => {
|
||||
for (const unsafePath of ["../escape.ngc", "programs/../escape.ngc", "/escape.ngc", "programs//escape.ngc"]) {
|
||||
await expectErrorContaining(
|
||||
() => simulator.fs.opfs.writeFile(unsafePath, "G21\n"),
|
||||
"inside the CNC workspace",
|
||||
`OPFS accepted unsafe path ${unsafePath}`,
|
||||
);
|
||||
}
|
||||
for (const [operation, action] of [
|
||||
["readFile", () => simulator.fs.opfs.readFile("../escape.ngc")],
|
||||
["readDirectory", () => simulator.fs.opfs.readDirectory("../escape")],
|
||||
["persistFile", () => simulator.fs.opfs.persistFile("../escape.ngc")],
|
||||
["persistDirectory", () => simulator.fs.opfs.persistDirectory("../escape")],
|
||||
["copyFile", () => simulator.fs.opfs.copyFile("../escape.ngc", "programs/escape.ngc")],
|
||||
["copyFileTarget", () => simulator.fs.opfs.copyFile("healthcheck.ngc", "../escape.ngc")],
|
||||
["exists", () => simulator.fs.opfs.exists("../escape.ngc")],
|
||||
["stat", () => simulator.fs.opfs.stat("../escape.ngc")],
|
||||
["moveFile", () => simulator.fs.opfs.moveFile("../escape.ngc", "programs/escape.ngc")],
|
||||
["moveFileTarget", () => simulator.fs.opfs.moveFile("healthcheck.ngc", "../escape.ngc")],
|
||||
["removeFile", () => simulator.fs.opfs.removeFile("../escape.ngc")],
|
||||
["removeDirectory", () => simulator.fs.opfs.removeDirectory("../escape")],
|
||||
["parseFile", () => simulator.parseFile("../escape.ngc", "linuxcnc", { backend: "linuxcnc-rs274" })],
|
||||
[
|
||||
"parseWithParameterFile",
|
||||
() =>
|
||||
simulator.parseWithParameterFile("G21 G90\nM30\n", "../escape.var", "linuxcnc", {
|
||||
backend: "linuxcnc-rs274",
|
||||
}),
|
||||
],
|
||||
]) {
|
||||
await expectErrorContaining(
|
||||
action,
|
||||
"inside the CNC workspace",
|
||||
`OPFS ${operation} accepted an unsafe path`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await runSection("opfs program parse and restore", async () => {
|
||||
const opfsProgramEvents = await simulator.parseFile("programs/browser-smoke.ngc", "linuxcnc", {
|
||||
backend: "linuxcnc-rs274",
|
||||
});
|
||||
expectEvent(opfsProgramEvents, (event) => event.type === "rapid" && near(event.end.x, 1),
|
||||
"OPFS program file was not parsed through LinuxCNC WASM");
|
||||
for (const parameterPath of ["rs274ngc.var", "rs274ngc.var.new", "rs274ngc.var.bak"]) {
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.stat(parameterPath),
|
||||
`browser parseFile left LinuxCNC parameter state outside OPFS: ${parameterPath}`,
|
||||
);
|
||||
}
|
||||
await withSmokeSimulator(async (restoredProgramSimulator) => {
|
||||
// LinuxCNC source basis: rs274ngc_pre.cc read_text() reads
|
||||
// blocks from the program stream after the OPFS mirror is loaded.
|
||||
const restoredProgramEvents = await restoredProgramSimulator.parseFile(
|
||||
"programs/browser-smoke.ngc",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
);
|
||||
expectEvent(restoredProgramEvents, (event) => event.type === "rapid" && near(event.end.x, 1),
|
||||
"LinuxCNC OPFS program file was not restored across WASM simulator instances");
|
||||
});
|
||||
});
|
||||
}
|
||||
174
web/test-browser-wasm-smoke-opfs-directory-sections.js
Normal file
174
web/test-browser-wasm-smoke-opfs-directory-sections.js
Normal file
@@ -0,0 +1,174 @@
|
||||
export async function runBrowserOpfsDirectorySections(context, simulator) {
|
||||
const {
|
||||
expectFileList,
|
||||
expectMissingOpfsFile,
|
||||
expectMissingWasmPath,
|
||||
expectText,
|
||||
runSection,
|
||||
withSmokeSimulator,
|
||||
} = context;
|
||||
|
||||
await runSection("opfs directory persist and load", async () => {
|
||||
const fixturePath = simulator.fs.opfs.resolvePath("fixtures");
|
||||
simulator.fs.module.FS.mkdirTree(`${fixturePath}/nested`);
|
||||
simulator.fs.module.FS.mkdirTree(`${fixturePath}/empty/nested`);
|
||||
simulator.fs.module.FS.writeFile(`${fixturePath}/nested/fixture.ngc`, "G21 G90\nG0 X3\n");
|
||||
simulator.fs.module.FS.writeFile(`${fixturePath}/root.ngc`, "G21 G90\nG0 X4\n");
|
||||
const persistedFiles = await simulator.fs.opfs.persistDirectory("fixtures");
|
||||
const fixtureStat = await simulator.fs.opfs.stat("fixtures");
|
||||
if (fixtureStat.kind !== "directory" || fixtureStat.size !== null) {
|
||||
throw new Error("OPFS stat did not report a persisted directory");
|
||||
}
|
||||
expectFileList(persistedFiles, ["nested/fixture.ngc", "root.ngc"],
|
||||
"unexpected OPFS directory persist file list");
|
||||
const fixtureDecoded = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("fixtures/nested/fixture.ngc"),
|
||||
);
|
||||
expectText(fixtureDecoded, "G21 G90\nG0 X3\n",
|
||||
"persisted WASM filesystem directory file was not readable from OPFS");
|
||||
const emptyFixtureStat = await simulator.fs.opfs.stat("fixtures/empty/nested");
|
||||
if (emptyFixtureStat.kind !== "directory" || emptyFixtureStat.size !== null) {
|
||||
throw new Error("OPFS persistDirectory did not preserve an empty directory");
|
||||
}
|
||||
await withSmokeSimulator(async (staleDirectorySimulator) => {
|
||||
await staleDirectorySimulator.fs.opfs.writeFile("fixtures/stale.ngc", "G21 G90\nG0 X6\n");
|
||||
});
|
||||
simulator.fs.module.FS.unlink(`${fixturePath}/nested/fixture.ngc`);
|
||||
const refreshedPersistedFiles = await simulator.fs.opfs.persistDirectory("fixtures");
|
||||
expectFileList(refreshedPersistedFiles, ["root.ngc"],
|
||||
"unexpected refreshed OPFS directory persist file list");
|
||||
await expectMissingOpfsFile(
|
||||
() => simulator.fs.opfs.readFile("fixtures/stale.ngc"),
|
||||
"stale OPFS directory file remained after directory persist",
|
||||
);
|
||||
await expectMissingOpfsFile(
|
||||
() => simulator.fs.opfs.readFile("fixtures/nested/fixture.ngc"),
|
||||
"removed WASM directory file remained in OPFS after directory persist",
|
||||
);
|
||||
simulator.fs.module.FS.writeFile(`${fixturePath}/nested/fixture.ngc`, "G21 G90\nG0 X3\n");
|
||||
const restoredPersistedFiles = await simulator.fs.opfs.persistDirectory("fixtures");
|
||||
expectFileList(restoredPersistedFiles, ["nested/fixture.ngc", "root.ngc"],
|
||||
"unexpected restored OPFS directory persist file list");
|
||||
const loadedFiles = await simulator.fs.opfs.readDirectory("fixtures");
|
||||
expectFileList(loadedFiles, ["nested/fixture.ngc", "root.ngc"],
|
||||
"unexpected OPFS directory load file list");
|
||||
const loadedRootPath = simulator.fs.opfs.resolvePath("fixtures/root.ngc");
|
||||
const loadedRootDecoded = new TextDecoder().decode(simulator.fs.module.FS.readFile(loadedRootPath));
|
||||
expectText(loadedRootDecoded, "G21 G90\nG0 X4\n",
|
||||
"OPFS directory file was not mirrored into the WASM filesystem");
|
||||
const loadedEmptyPath = simulator.fs.opfs.resolvePath("fixtures/empty/nested");
|
||||
const loadedEmptyMode = simulator.fs.module.FS.stat(loadedEmptyPath).mode;
|
||||
if (!simulator.fs.module.FS.isDir(loadedEmptyMode)) {
|
||||
throw new Error("OPFS readDirectory did not mirror an empty directory into WASM FS");
|
||||
}
|
||||
});
|
||||
|
||||
await runSection("opfs directory refresh and type conflicts", async () => {
|
||||
const loadedRootPath = simulator.fs.opfs.resolvePath("fixtures/root.ngc");
|
||||
await withSmokeSimulator(async (updateDirectorySimulator) => {
|
||||
await updateDirectorySimulator.fs.opfs.removeFile("fixtures/root.ngc");
|
||||
await updateDirectorySimulator.fs.opfs.writeFile("fixtures/nested/fixture.ngc", "G21 G90\nG0 X5\n");
|
||||
const refreshedFiles = await simulator.fs.opfs.readDirectory("fixtures");
|
||||
expectFileList(refreshedFiles, ["nested/fixture.ngc"],
|
||||
"unexpected refreshed OPFS directory file list");
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.stat(loadedRootPath),
|
||||
"stale OPFS directory mirror file remained in WASM FS",
|
||||
);
|
||||
const refreshedFixturePath = simulator.fs.opfs.resolvePath("fixtures/nested/fixture.ngc");
|
||||
const refreshedFixture = new TextDecoder().decode(
|
||||
simulator.fs.module.FS.readFile(refreshedFixturePath),
|
||||
);
|
||||
expectText(refreshedFixture, "G21 G90\nG0 X5\n",
|
||||
"OPFS directory refresh did not replace the WASM mirror file");
|
||||
await simulator.fs.opfs.writeFile("fixtures/type-conflict/original.ngc", "G21 G90\n");
|
||||
await updateDirectorySimulator.fs.opfs.removeDirectory("fixtures/type-conflict");
|
||||
await updateDirectorySimulator.fs.opfs.writeFile("fixtures/type-conflict", "G21 G90\nG0 X11\n");
|
||||
const replacedConflictFile = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("fixtures/type-conflict"),
|
||||
);
|
||||
expectText(replacedConflictFile, "G21 G90\nG0 X11\n",
|
||||
"OPFS readFile did not load a file that replaced a directory");
|
||||
const conflictMirrorPath = simulator.fs.opfs.resolvePath("fixtures/type-conflict");
|
||||
const conflictMirrorMode = simulator.fs.module.FS.stat(conflictMirrorPath).mode;
|
||||
if (!simulator.fs.module.FS.isFile(conflictMirrorMode)) {
|
||||
throw new Error("OPFS readFile did not replace the WASM mirror directory with a file");
|
||||
}
|
||||
await updateDirectorySimulator.fs.opfs.removeFile("fixtures/type-conflict");
|
||||
await updateDirectorySimulator.fs.opfs.writeFile(
|
||||
"fixtures/type-conflict/original.ngc",
|
||||
"G21 G90\n",
|
||||
);
|
||||
await updateDirectorySimulator.fs.opfs.removeDirectory("fixtures/type-conflict");
|
||||
await updateDirectorySimulator.fs.opfs.writeFile("fixtures/root.ngc", "G21 G90\nG0 X4\n");
|
||||
await updateDirectorySimulator.fs.opfs.writeFile("fixtures/nested/fixture.ngc", "G21 G90\nG0 X3\n");
|
||||
const restoredFiles = await simulator.fs.opfs.readDirectory("fixtures");
|
||||
expectFileList(restoredFiles, ["nested/fixture.ngc", "root.ngc"],
|
||||
"unexpected reloaded OPFS directory file list");
|
||||
});
|
||||
});
|
||||
|
||||
await runSection("opfs directory restore in new instance", async () => {
|
||||
await withSmokeSimulator(async (restoredDirectorySimulator) => {
|
||||
const restoredDirectoryFiles = await restoredDirectorySimulator.fs.opfs.readDirectory("fixtures");
|
||||
expectFileList(restoredDirectoryFiles, ["nested/fixture.ngc", "root.ngc"],
|
||||
"unexpected restored OPFS directory file list");
|
||||
const restoredFixtureRootPath = restoredDirectorySimulator.fs.opfs.resolvePath("fixtures/root.ngc");
|
||||
const restoredFixtureRoot = new TextDecoder().decode(
|
||||
restoredDirectorySimulator.fs.module.FS.readFile(restoredFixtureRootPath),
|
||||
);
|
||||
expectText(restoredFixtureRoot, "G21 G90\nG0 X4\n",
|
||||
"OPFS directory was not restored into a new WASM filesystem instance");
|
||||
const restoredEmptyFixturePath = restoredDirectorySimulator.fs.opfs.resolvePath("fixtures/empty/nested");
|
||||
const restoredEmptyFixtureMode =
|
||||
restoredDirectorySimulator.fs.module.FS.stat(restoredEmptyFixturePath).mode;
|
||||
if (!restoredDirectorySimulator.fs.module.FS.isDir(restoredEmptyFixtureMode)) {
|
||||
throw new Error("OPFS empty directory was not restored into a new WASM filesystem instance");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await runSection("opfs metadata queries avoid wasm mirror pollution", async () => {
|
||||
await withSmokeSimulator(async (existsOnlySimulator) => {
|
||||
await existsOnlySimulator.fs.opfs.writeFile("programs/exists-only.ngc", "G21 G90\nG0 X10\n");
|
||||
const existsOnlyPath = simulator.fs.opfs.resolvePath("programs/exists-only.ngc");
|
||||
if (!(await simulator.fs.opfs.exists("programs/exists-only.ngc"))) {
|
||||
throw new Error("OPFS exists did not see a file written by another simulator instance");
|
||||
}
|
||||
const existsOnlyStat = await simulator.fs.opfs.stat("programs/exists-only.ngc");
|
||||
if (existsOnlyStat.kind !== "file" || existsOnlyStat.size !== 15) {
|
||||
throw new Error("OPFS stat did not see a file written by another simulator instance");
|
||||
}
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.stat(existsOnlyPath),
|
||||
"OPFS metadata queries polluted the WASM filesystem mirror",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
await runSection("opfs workspace clear and mirror removal", async () => {
|
||||
await simulator.fs.opfs.clear();
|
||||
await withSmokeSimulator(async (clearedWorkspaceSimulator) => {
|
||||
await expectMissingOpfsFile(
|
||||
() => clearedWorkspaceSimulator.fs.opfs.readDirectory("fixtures"),
|
||||
"cleared OPFS workspace still exposed a persisted directory",
|
||||
);
|
||||
});
|
||||
await simulator.fs.opfs.removeFile("programs/browser-smoke.ngc");
|
||||
if (await simulator.fs.opfs.exists("programs/browser-smoke.ngc")) {
|
||||
throw new Error("OPFS exists reported a removed file");
|
||||
}
|
||||
const removedProgramPath = simulator.fs.opfs.resolvePath("programs/browser-smoke.ngc");
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.stat(removedProgramPath),
|
||||
"OPFS removeFile did not remove the WASM mirror",
|
||||
);
|
||||
const fixturePath = simulator.fs.opfs.resolvePath("fixtures");
|
||||
await simulator.fs.opfs.removeDirectory("fixtures");
|
||||
await simulator.fs.opfs.removeDirectory("fixtures");
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.stat(fixturePath),
|
||||
"OPFS removeDirectory did not remove the WASM mirror",
|
||||
);
|
||||
});
|
||||
}
|
||||
76
web/test-browser-wasm-smoke-opfs-mirror-sections.js
Normal file
76
web/test-browser-wasm-smoke-opfs-mirror-sections.js
Normal file
@@ -0,0 +1,76 @@
|
||||
export async function runBrowserOpfsMirrorSections(context, simulator) {
|
||||
const {
|
||||
expectMissingWasmPath,
|
||||
expectText,
|
||||
runSection,
|
||||
} = context;
|
||||
|
||||
await runSection("opfs wasm mirror persist file", async () => {
|
||||
const wasmPath = simulator.fs.opfs.resolvePath("programs/browser-smoke.ngc");
|
||||
const wasmDecoded = new TextDecoder().decode(simulator.fs.module.FS.readFile(wasmPath));
|
||||
expectText(wasmDecoded, "G21 G90\nG0 X1\n", "OPFS file was not mirrored into the WASM filesystem");
|
||||
const persistedPath = simulator.fs.opfs.resolvePath("programs/browser-persisted.ngc");
|
||||
simulator.fs.module.FS.writeFile(persistedPath, new TextEncoder().encode("G21 G90\nG0 X2\n"));
|
||||
const persisted = new TextDecoder().decode(await simulator.fs.opfs.persistFile("programs/browser-persisted.ngc"));
|
||||
expectText(persisted, "G21 G90\nG0 X2\n", "WASM filesystem file was not persisted into OPFS");
|
||||
const persistedDecoded = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("programs/browser-persisted.ngc"),
|
||||
);
|
||||
expectText(persistedDecoded, "G21 G90\nG0 X2\n",
|
||||
"persisted WASM filesystem file was not readable from OPFS");
|
||||
});
|
||||
|
||||
await runSection("opfs copy and move mirror files", async () => {
|
||||
// LinuxCNC source basis: rs274ngc_pre.cc save_parameters()
|
||||
// links the current parameter file to filename + ".bak", then
|
||||
// writes filename + ".new" and renames it over the parameter file.
|
||||
await simulator.fs.opfs.writeFile("parameters/move-main.var", "5161\t1.000000\n");
|
||||
const copiedParameterBackup = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.copyFile("parameters/move-main.var", "parameters/move-main.var.bak"),
|
||||
);
|
||||
expectText(copiedParameterBackup, "5161\t1.000000\n",
|
||||
"OPFS copyFile did not return the copied file contents");
|
||||
const copiedParameterBackupFile = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("parameters/move-main.var.bak"),
|
||||
);
|
||||
expectText(copiedParameterBackupFile, "5161\t1.000000\n",
|
||||
"OPFS copyFile did not create the backup file");
|
||||
const copiedParameterBackupWasmPath = simulator.fs.opfs.resolvePath("parameters/move-main.var.bak");
|
||||
const copiedParameterBackupWasm = new TextDecoder().decode(
|
||||
simulator.fs.module.FS.readFile(copiedParameterBackupWasmPath),
|
||||
);
|
||||
expectText(copiedParameterBackupWasm, "5161\t1.000000\n",
|
||||
"OPFS copyFile did not update the WASM mirror destination");
|
||||
await simulator.fs.opfs.writeFile("parameters/move-main.var.new", "5161\t2.000000\n");
|
||||
await simulator.fs.opfs.moveFile("parameters/move-main.var.new", "parameters/move-main.var");
|
||||
const movedParameterFile = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("parameters/move-main.var"),
|
||||
);
|
||||
expectText(movedParameterFile, "5161\t2.000000\n",
|
||||
"OPFS moveFile did not replace the destination file");
|
||||
if (await simulator.fs.opfs.exists("parameters/move-main.var.new")) {
|
||||
throw new Error("OPFS moveFile left the source file behind");
|
||||
}
|
||||
const movedParameterWasmPath = simulator.fs.opfs.resolvePath("parameters/move-main.var");
|
||||
const movedParameterWasm = new TextDecoder().decode(
|
||||
simulator.fs.module.FS.readFile(movedParameterWasmPath),
|
||||
);
|
||||
expectText(movedParameterWasm, "5161\t2.000000\n",
|
||||
"OPFS moveFile did not update the WASM mirror destination");
|
||||
const movedTemporaryWasmPath = simulator.fs.opfs.resolvePath("parameters/move-main.var.new");
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.stat(movedTemporaryWasmPath),
|
||||
"OPFS moveFile left the WASM mirror source behind",
|
||||
);
|
||||
});
|
||||
|
||||
await runSection("opfs write truncates wasm mirror", async () => {
|
||||
await simulator.fs.opfs.writeFile("programs/truncate.ngc", "G21 G90\nG0 X12345\nM30\n");
|
||||
await simulator.fs.opfs.writeFile("programs/truncate.ngc", "G21\n");
|
||||
const truncatedProgram = new TextDecoder().decode(await simulator.fs.opfs.readFile("programs/truncate.ngc"));
|
||||
expectText(truncatedProgram, "G21\n", "OPFS writeFile did not truncate stale program bytes");
|
||||
const truncatedWasmPath = simulator.fs.opfs.resolvePath("programs/truncate.ngc");
|
||||
const truncatedWasmProgram = new TextDecoder().decode(simulator.fs.module.FS.readFile(truncatedWasmPath));
|
||||
expectText(truncatedWasmProgram, "G21\n", "OPFS writeFile did not truncate the WASM mirror");
|
||||
});
|
||||
}
|
||||
255
web/test-browser-wasm-smoke-opfs-parameter-sections.js
Normal file
255
web/test-browser-wasm-smoke-opfs-parameter-sections.js
Normal file
@@ -0,0 +1,255 @@
|
||||
export async function runBrowserOpfsParameterSections(context, simulator) {
|
||||
const {
|
||||
expectErrorContaining,
|
||||
expectEvent,
|
||||
expectIncludes,
|
||||
expectMissingOpfsFile,
|
||||
expectMissingWasmPath,
|
||||
expectNoEvent,
|
||||
expectText,
|
||||
expectWasmFilesMissing,
|
||||
near,
|
||||
removeWasmPathIfExists,
|
||||
runSection,
|
||||
withSmokeSimulator,
|
||||
} = context;
|
||||
|
||||
await runSection("opfs parameter save", async () => {
|
||||
await simulator.parseWithParameterFile(
|
||||
"G21 G90\nG0 X1\nG28.1\nM30\n",
|
||||
"parameters/rs274ngc.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
);
|
||||
const opfsParameterFile = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("parameters/rs274ngc.var"),
|
||||
);
|
||||
expectIncludes(opfsParameterFile, "5161\t1.000000", "LinuxCNC parameter file was not persisted into OPFS");
|
||||
});
|
||||
|
||||
await runSection("opfs parameter restore in new instance", async () => {
|
||||
await withSmokeSimulator(async (restoredWorkspaceSimulator) => {
|
||||
// LinuxCNC source basis: rs274ngc_pre.cc restore_parameters()
|
||||
// loads persisted numbered parameters before interpreting blocks.
|
||||
const restoredWorkspaceEvents = await restoredWorkspaceSimulator.parseWithParameterFile(
|
||||
"G21 G90\nF100\nO10 if [#5161 EQ 1]\nG1 X8\nO10 endif\n",
|
||||
"parameters/rs274ngc.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
);
|
||||
expectEvent(restoredWorkspaceEvents, (event) => event.type === "linear-feed" && near(event.end.x, 8),
|
||||
"LinuxCNC OPFS parameter file was not restored across WASM simulator instances");
|
||||
});
|
||||
});
|
||||
|
||||
await runSection("opfs parameter temporary cleanup", async () => {
|
||||
await simulator.fs.opfs.writeFile("parameters/rs274ngc.var.new", "stale temporary parameter file\n");
|
||||
// LinuxCNC source basis: rs274ngc_pre.cc save_parameters() writes
|
||||
// filename + ".new", then renames it over the parameter file.
|
||||
simulator.fs.module.FS.writeFile("rs274ngc.var.new", "stale wasm temporary parameter file\n");
|
||||
const restoredParameterEvents = await simulator.parseWithParameterFile(
|
||||
"G21 G90\nF100\nO10 if [#5161 EQ 1]\nG1 X9\nO10 endif\n",
|
||||
"parameters/rs274ngc.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
);
|
||||
expectEvent(restoredParameterEvents, (event) => event.type === "linear-feed" && near(event.end.x, 9),
|
||||
"LinuxCNC parameter file was not restored from OPFS");
|
||||
await expectMissingOpfsFile(
|
||||
() => simulator.fs.opfs.readFile("parameters/rs274ngc.var.new"),
|
||||
"stale LinuxCNC temporary parameter file remained in OPFS",
|
||||
);
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.readFile("rs274ngc.var.new"),
|
||||
"stale LinuxCNC temporary parameter file remained in WASM FS",
|
||||
);
|
||||
});
|
||||
|
||||
await runSection("opfs parameter backup bridge", async () => {
|
||||
// LinuxCNC source basis: rs274ngc_pre.cc save_parameters()
|
||||
// attempts link(filename, filename + ".bak") but treats link
|
||||
// failure as non-fatal. The browser bridge must persist a backup
|
||||
// when the WASM filesystem exposes one.
|
||||
simulator.fs.module.FS.writeFile("rs274ngc.var", "5161\t2.000000\n");
|
||||
simulator.fs.module.FS.writeFile("rs274ngc.var.bak", "5161\t1.000000\n");
|
||||
await simulator.fs.opfs.persistParameterFile("parameters/backup-bridge.var");
|
||||
const opfsParameterBackup = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("parameters/backup-bridge.var.bak"),
|
||||
);
|
||||
expectIncludes(opfsParameterBackup, "5161\t1.000000",
|
||||
"LinuxCNC parameter backup file was not bridged into OPFS");
|
||||
});
|
||||
|
||||
await runSection("opfs parameter stale backup cleanup", async () => {
|
||||
removeWasmPathIfExists(simulator, "rs274ngc.var.bak");
|
||||
removeWasmPathIfExists(simulator, "/rs274ngc.var.bak");
|
||||
await simulator.parseWithParameterFile(
|
||||
"G21 G90\nM30\n",
|
||||
"parameters/rs274ngc.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
);
|
||||
await expectMissingOpfsFile(
|
||||
() => simulator.fs.opfs.readFile("parameters/rs274ngc.var.bak"),
|
||||
"stale LinuxCNC parameter backup file remained in OPFS",
|
||||
);
|
||||
// LinuxCNC source basis: rs274ngc_pre.cc save_parameters()
|
||||
// unlinks stale backup files before replacing the parameter file.
|
||||
await simulator.fs.opfs.removeFile("parameters/missing-cleanup/rs274ngc.var.bak");
|
||||
});
|
||||
|
||||
await runSection("opfs program file with parameter persistence", async () => {
|
||||
await simulator.fs.opfs.writeFile("programs/parameter-file-smoke.ngc", "G21 G90\nF100\nG0 X1\nG28.1\nM30\n");
|
||||
const fileParameterEvents = await simulator.parseFileWithParameterFile(
|
||||
"programs/parameter-file-smoke.ngc",
|
||||
"parameters/rs274ngc.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
);
|
||||
expectEvent(fileParameterEvents, (event) => event.type === "program-end",
|
||||
"OPFS program file with LinuxCNC parameter persistence did not parse");
|
||||
});
|
||||
|
||||
await runSection("opfs missing parameter file creation", async () => {
|
||||
await simulator.fs.opfs.writeFile("parameters/missing-main.var.bak", "stale backup without main file\n");
|
||||
await simulator.fs.opfs.writeFile("parameters/missing-main.var.new", "stale temporary without main file\n");
|
||||
// LinuxCNC source basis: rs274ngc_pre.cc save_parameters() unlinks
|
||||
// filename + RS274NGC_PARAMETER_FILE_BACKUP_SUFFIX before linking,
|
||||
// then renames filename + ".new" over the parameter file.
|
||||
simulator.fs.module.FS.writeFile("rs274ngc.var.bak", "stale wasm backup without main file\n");
|
||||
simulator.fs.module.FS.writeFile("rs274ngc.var.new", "stale wasm temporary without main file\n");
|
||||
await simulator.parseWithParameterFile(
|
||||
"G21 G90\nM30\n",
|
||||
"parameters/missing-main.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
);
|
||||
const missingMainParameterFile = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("parameters/missing-main.var"),
|
||||
);
|
||||
expectIncludes(missingMainParameterFile, "5220\t1.000000",
|
||||
"LinuxCNC missing parameter file was not created through OPFS");
|
||||
expectIncludes(missingMainParameterFile, "5161\t0.000000",
|
||||
"LinuxCNC missing parameter file did not include required G28 parameter");
|
||||
const missingMainRestoreEvents = await simulator.parseWithParameterFile(
|
||||
"G21 G90\nF100\nO10 if [#5220 EQ 1]\nG1 X6\nO10 endif\n",
|
||||
"parameters/missing-main.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
);
|
||||
expectEvent(missingMainRestoreEvents, (event) => event.type === "linear-feed" && near(event.end.x, 6),
|
||||
"LinuxCNC-created missing OPFS parameter file was not restored on the next parse");
|
||||
await expectMissingOpfsFile(
|
||||
() => simulator.fs.opfs.readFile("parameters/missing-main.var.bak"),
|
||||
"stale LinuxCNC missing-parameter backup remained in OPFS",
|
||||
);
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.readFile("rs274ngc.var.bak"),
|
||||
"stale LinuxCNC missing-parameter backup remained in WASM FS",
|
||||
);
|
||||
await expectMissingOpfsFile(
|
||||
() => simulator.fs.opfs.readFile("parameters/missing-main.var.new"),
|
||||
"stale LinuxCNC missing-parameter temporary file remained in OPFS",
|
||||
);
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.readFile("rs274ngc.var.new"),
|
||||
"stale LinuxCNC missing-parameter temporary file remained in WASM FS",
|
||||
);
|
||||
});
|
||||
|
||||
await runSection("opfs invalid parameter restore failures", async () => {
|
||||
await simulator.fs.opfs.writeFile("parameters/out-of-order.var", "5162\t1.000000\n5161\t1.000000\n");
|
||||
await expectErrorContaining(
|
||||
() => simulator.parseWithParameterFile(
|
||||
"G21 G90\nM30\n",
|
||||
"parameters/out-of-order.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
),
|
||||
"Parameter file out of order",
|
||||
"LinuxCNC accepted an out-of-order OPFS parameter file",
|
||||
);
|
||||
const outOfOrderParameterFile = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("parameters/out-of-order.var"),
|
||||
);
|
||||
expectText(outOfOrderParameterFile, "5162\t1.000000\n5161\t1.000000\n",
|
||||
"LinuxCNC persisted an out-of-order OPFS parameter file after restore failure");
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.readFile("rs274ngc.var"),
|
||||
"LinuxCNC out-of-order parameter file remained in WASM FS after restore failure",
|
||||
);
|
||||
await simulator.fs.opfs.writeFile("parameters/out-of-range.var", "0\t1.000000\n");
|
||||
await expectErrorContaining(
|
||||
() => simulator.parseWithParameterFile(
|
||||
"G21 G90\nM30\n",
|
||||
"parameters/out-of-range.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
),
|
||||
"Parameter number out of range",
|
||||
"LinuxCNC accepted an out-of-range OPFS parameter file",
|
||||
);
|
||||
const outOfRangeParameterFile = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("parameters/out-of-range.var"),
|
||||
);
|
||||
expectText(outOfRangeParameterFile, "0\t1.000000\n",
|
||||
"LinuxCNC persisted an out-of-range OPFS parameter file after restore failure");
|
||||
expectMissingWasmPath(
|
||||
() => simulator.fs.module.FS.readFile("rs274ngc.var"),
|
||||
"LinuxCNC out-of-range parameter file remained in WASM FS after restore failure",
|
||||
);
|
||||
await simulator.fs.opfs.writeFile("parameters/failed-parse.var", "1\t0.000000\n");
|
||||
await expectErrorContaining(
|
||||
() => simulator.parseWithParameterFile(
|
||||
"G21 G90\nG28.1\n#0 = 1\n",
|
||||
"parameters/failed-parse.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
),
|
||||
"",
|
||||
"LinuxCNC accepted a failing parse that should not persist parameters",
|
||||
);
|
||||
const failedParseParameterFile = new TextDecoder().decode(
|
||||
await simulator.fs.opfs.readFile("parameters/failed-parse.var"),
|
||||
);
|
||||
if (failedParseParameterFile.includes("5161\t1.000000")) {
|
||||
throw new Error("LinuxCNC persisted failed parse parameter updates into OPFS");
|
||||
}
|
||||
});
|
||||
|
||||
await runSection("opfs stale parameter state cleanup", async () => {
|
||||
await simulator.fs.opfs.writeFile("parameters/stale-state.var", "5161\t1.000000\n");
|
||||
const staleStateEvents = await simulator.parseWithParameterFile(
|
||||
"G21 G90\nF100\nO10 if [#5161 EQ 1]\nG1 X9\nO10 endif\n",
|
||||
"parameters/stale-state.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
);
|
||||
expectEvent(staleStateEvents, (event) => event.type === "linear-feed" && near(event.end.x, 9),
|
||||
"LinuxCNC did not honor the loaded OPFS parameter state");
|
||||
await simulator.fs.opfs.removeFile("parameters/stale-state.var");
|
||||
const missingStateEvents = await simulator.parseWithParameterFile(
|
||||
"G21 G90\nF100\nO10 if [#5161 EQ 1]\nG1 X9\nO10 endif\n",
|
||||
"parameters/stale-state.var",
|
||||
"linuxcnc",
|
||||
{ backend: "linuxcnc-rs274" },
|
||||
);
|
||||
expectNoEvent(missingStateEvents, (event) => event.type === "linear-feed" && near(event.end.x, 9),
|
||||
"LinuxCNC stale OPFS parameter state was not cleared when the file was missing");
|
||||
// LinuxCNC source basis: rs274ngc_pre.cc restore_parameters()
|
||||
// treats a missing parameter file as OK; the browser OPFS bridge
|
||||
// must not leave stale default parameter files in the WASM mirror.
|
||||
simulator.fs.module.FS.writeFile("rs274ngc.var", "5161\t1.000000\n");
|
||||
simulator.fs.module.FS.writeFile("rs274ngc.var.new", "stale temporary parameter file\n");
|
||||
simulator.fs.module.FS.writeFile("rs274ngc.var.bak", "stale backup parameter file\n");
|
||||
const missingLoad = await simulator.fs.opfs.loadParameterFile("parameters/load-missing.var");
|
||||
if (missingLoad !== null) {
|
||||
throw new Error("missing OPFS parameter file did not return null");
|
||||
}
|
||||
expectWasmFilesMissing(
|
||||
simulator.fs.module.FS,
|
||||
["rs274ngc.var", "rs274ngc.var.new", "rs274ngc.var.bak"],
|
||||
(parameterPath) => `missing OPFS parameter load left stale WASM file ${parameterPath}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
49
web/test-browser-wasm-smoke-opfs-policy-sections.js
Normal file
49
web/test-browser-wasm-smoke-opfs-policy-sections.js
Normal file
@@ -0,0 +1,49 @@
|
||||
export async function runBrowserOpfsPolicySections(context) {
|
||||
const {
|
||||
browserHasOpfs,
|
||||
createDefaultBrowserSimulator,
|
||||
createSmokeSimulator,
|
||||
expectErrorContaining,
|
||||
runSection,
|
||||
} = context;
|
||||
if (!browserHasOpfs()) {
|
||||
return;
|
||||
}
|
||||
await runSection("browser opfs policy", async () => {
|
||||
const defaultOpfsSimulator = await createDefaultBrowserSimulator();
|
||||
try {
|
||||
if (!defaultOpfsSimulator.fs.opfs) {
|
||||
throw new Error("default browser simulator did not create an OPFS workspace");
|
||||
}
|
||||
if (defaultOpfsSimulator.fs.opfs.workspacePath !== "cnc-simulator") {
|
||||
throw new Error("default browser simulator used an unexpected OPFS workspace path");
|
||||
}
|
||||
} finally {
|
||||
defaultOpfsSimulator.dispose();
|
||||
}
|
||||
await expectErrorContaining(
|
||||
() => createSmokeSimulator(false),
|
||||
"OPFS cannot be disabled",
|
||||
"browser simulator allowed OPFS to be disabled",
|
||||
);
|
||||
for (const unsafeWorkspacePath of ["../bad-workspace", "/bad-workspace", "bad//workspace"]) {
|
||||
await expectErrorContaining(
|
||||
() => createSmokeSimulator({ workspacePath: unsafeWorkspacePath }),
|
||||
"inside the CNC workspace",
|
||||
`browser simulator accepted unsafe OPFS workspace ${unsafeWorkspacePath}`,
|
||||
);
|
||||
}
|
||||
for (const unsafeMountPoint of ["cnc", "/cnc/../escape", "/cnc//escape"]) {
|
||||
await expectErrorContaining(
|
||||
() => createSmokeSimulator({ mountPoint: unsafeMountPoint, workspacePath: "cnc-simulator-smoke" }),
|
||||
"Emscripten filesystem",
|
||||
`browser simulator accepted unsafe OPFS mount ${unsafeMountPoint}`,
|
||||
);
|
||||
}
|
||||
await expectErrorContaining(
|
||||
() => createSmokeSimulator({ mountPoint: "", workspacePath: "cnc-simulator-smoke" }),
|
||||
"non-empty absolute path",
|
||||
"browser simulator accepted empty OPFS mount",
|
||||
);
|
||||
});
|
||||
}
|
||||
14
web/test-browser-wasm-smoke-opfs-workspace-sections.js
Normal file
14
web/test-browser-wasm-smoke-opfs-workspace-sections.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { runBrowserOpfsBasicSections } from "/test-browser-wasm-smoke-opfs-basic-sections.js";
|
||||
import { runBrowserOpfsDirectorySections } from "/test-browser-wasm-smoke-opfs-directory-sections.js";
|
||||
import { runBrowserOpfsMirrorSections } from "/test-browser-wasm-smoke-opfs-mirror-sections.js";
|
||||
import { runBrowserOpfsParameterSections } from "/test-browser-wasm-smoke-opfs-parameter-sections.js";
|
||||
|
||||
export async function runBrowserOpfsWorkspaceSections(context, simulator) {
|
||||
if (!context.browserHasOpfs()) {
|
||||
return;
|
||||
}
|
||||
await runBrowserOpfsBasicSections(context, simulator);
|
||||
await runBrowserOpfsParameterSections(context, simulator);
|
||||
await runBrowserOpfsMirrorSections(context, simulator);
|
||||
await runBrowserOpfsDirectorySections(context, simulator);
|
||||
}
|
||||
99
web/test-browser-wasm-smoke-sections.js
Normal file
99
web/test-browser-wasm-smoke-sections.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import { createBrowserSmokeHarness } from "/test-browser-wasm-smoke-helpers.js";
|
||||
import { runBrowserAppOpfsSections } from "/test-browser-wasm-smoke-app-sections.js";
|
||||
import { runLinuxCncBrowserSections } from "/test-browser-wasm-smoke-linuxcnc-sections.js";
|
||||
import { runBrowserOpfsPolicySections } from "/test-browser-wasm-smoke-opfs-policy-sections.js";
|
||||
import { runBrowserOpfsWorkspaceSections } from "/test-browser-wasm-smoke-opfs-workspace-sections.js";
|
||||
|
||||
export async function runBrowserWasmSmoke({ createWasmSimulator, result }) {
|
||||
const harness = createBrowserSmokeHarness(result);
|
||||
const createSmokeSimulator = (opfs = { mountPoint: "/cnc", workspacePath: "cnc-simulator-smoke" }) =>
|
||||
createWasmSimulator({
|
||||
locateFile: (file) => `/public/${file}`,
|
||||
print: () => {},
|
||||
printErr: () => {},
|
||||
...(opfs === undefined ? {} : { opfs }),
|
||||
});
|
||||
const createDefaultBrowserSimulator = () =>
|
||||
createWasmSimulator({
|
||||
locateFile: (file) => `/public/${file}`,
|
||||
print: () => {},
|
||||
printErr: () => {},
|
||||
});
|
||||
const withSmokeSimulator = async (action, opfs) => {
|
||||
const nestedSimulator = await createSmokeSimulator(opfs);
|
||||
try {
|
||||
return await action(nestedSimulator);
|
||||
} finally {
|
||||
nestedSimulator.dispose();
|
||||
}
|
||||
};
|
||||
const createHiddenAppFrame = async () => {
|
||||
const appFrame = document.createElement("iframe");
|
||||
const appHtml = await (await fetch("/")).text();
|
||||
appFrame.srcdoc = appHtml.replace(
|
||||
"</head>",
|
||||
'<script src="/public/cnc_sim.js"><\/script></head>',
|
||||
);
|
||||
appFrame.style.display = "none";
|
||||
document.body.appendChild(appFrame);
|
||||
await new Promise((resolve) => {
|
||||
appFrame.addEventListener("load", resolve, { once: true });
|
||||
});
|
||||
return appFrame;
|
||||
};
|
||||
const waitForAppControls = (appFrame, message) =>
|
||||
harness.waitFor(
|
||||
() => appFrame.contentDocument?.querySelector("#parseBtn"),
|
||||
message,
|
||||
);
|
||||
const parseAppFrameProgram = (appFrame, program) => {
|
||||
const appInput = appFrame.contentDocument.querySelector("#programInput");
|
||||
appInput.value = program;
|
||||
appInput.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
appFrame.contentDocument.querySelector("#parseBtn").click();
|
||||
};
|
||||
const waitForAppAlarm = (appFrame, text, message) =>
|
||||
harness.waitFor(
|
||||
() => appFrame.contentDocument?.querySelector("#alarmList")?.textContent.includes(text),
|
||||
message,
|
||||
);
|
||||
const getAppFrameSimulator = async (appFrame, message) => {
|
||||
const appSimulator = await appFrame.contentWindow.__cncSimulatorForTest;
|
||||
if (!appSimulator?.fs?.opfs) {
|
||||
throw new Error(message);
|
||||
}
|
||||
return appSimulator;
|
||||
};
|
||||
const disposeAppFrame = async (appFrame) => {
|
||||
const appSimulator = await appFrame.contentWindow?.__cncSimulatorForTest;
|
||||
appSimulator?.dispose?.();
|
||||
appFrame.remove();
|
||||
};
|
||||
const context = {
|
||||
...harness,
|
||||
createDefaultBrowserSimulator,
|
||||
createHiddenAppFrame,
|
||||
createSmokeSimulator,
|
||||
disposeAppFrame,
|
||||
getAppFrameSimulator,
|
||||
parseAppFrameProgram,
|
||||
waitForAppAlarm,
|
||||
waitForAppControls,
|
||||
withSmokeSimulator,
|
||||
};
|
||||
|
||||
try {
|
||||
const simulator = await createSmokeSimulator();
|
||||
try {
|
||||
await runLinuxCncBrowserSections(context, simulator);
|
||||
await runBrowserOpfsWorkspaceSections(context, simulator);
|
||||
await runBrowserOpfsPolicySections(context);
|
||||
await runBrowserAppOpfsSections(context);
|
||||
result.textContent = harness.completedMessage();
|
||||
} finally {
|
||||
simulator.dispose();
|
||||
}
|
||||
} catch (error) {
|
||||
result.textContent = `browser wasm smoke failed: ${error && error.stack ? error.stack : error}`;
|
||||
}
|
||||
}
|
||||
@@ -6,82 +6,16 @@
|
||||
</head>
|
||||
<body>
|
||||
<pre id="result">pending</pre>
|
||||
<!-- smoke marker: browser wasm smoke passed -->
|
||||
<script src="/public/cnc_sim.js"></script>
|
||||
<script type="module">
|
||||
import { createWasmSimulator } from "/src/wasm-core.js";
|
||||
import { runBrowserWasmSmoke } from "/test-browser-wasm-smoke-sections.js";
|
||||
|
||||
const result = document.querySelector("#result");
|
||||
const near = (actual, expected) => Math.abs(actual - expected) < 1e-6;
|
||||
|
||||
try {
|
||||
const simulator = await createWasmSimulator({
|
||||
locateFile: (file) => `/public/${file}`,
|
||||
print: () => {},
|
||||
printErr: () => {},
|
||||
});
|
||||
try {
|
||||
const events = simulator.parse("G21 G90\nG0 X0\nG1 X5 F100\nM30\n", "linuxcnc", {
|
||||
backend: "linuxcnc-rs274",
|
||||
});
|
||||
const sawFeed = events.some((event) => event.type === "linear-feed" && near(event.end.x, 5));
|
||||
const sawEnd = events.some((event) => event.type === "program-end");
|
||||
if (!sawFeed || !sawEnd) {
|
||||
throw new Error("missing expected LinuxCNC WASM browser events");
|
||||
}
|
||||
const switchEvents = simulator.parse("M428\nM429\nM430\n", "linuxcnc", {
|
||||
backend: "linuxcnc-rs274",
|
||||
switchkins: "scara",
|
||||
});
|
||||
for (const [line, kinstype] of [
|
||||
[1, 0],
|
||||
[2, 1],
|
||||
[3, 2],
|
||||
]) {
|
||||
const sawSwitch = switchEvents.some(
|
||||
(event) => event.type === "kinematics-switch" && event.line === line && event.reserved === kinstype,
|
||||
);
|
||||
if (!sawSwitch) {
|
||||
throw new Error(`missing LinuxCNC WASM browser M${427 + line} switchkins type ${kinstype}`);
|
||||
}
|
||||
}
|
||||
|
||||
const configCaseResponse = await fetch("/public/linuxcnc_switchkins_remap_config_cases.json");
|
||||
if (!configCaseResponse.ok) {
|
||||
throw new Error(`failed to load LinuxCNC switchkins config cases: ${configCaseResponse.status}`);
|
||||
}
|
||||
const configCases = await configCaseResponse.json();
|
||||
if (!Array.isArray(configCases) || configCases.length === 0) {
|
||||
throw new Error("missing LinuxCNC switchkins config cases");
|
||||
}
|
||||
for (const configCase of configCases) {
|
||||
const options = {
|
||||
backend: "linuxcnc-rs274",
|
||||
[configCase.field]: configCase.value,
|
||||
};
|
||||
const program = configCase.m430 >= 0 ? "M428\nM429\nM430\n" : "M428\nM429\n";
|
||||
const configEvents = simulator.parse(program, "linuxcnc", options);
|
||||
for (const [line, kinstype] of [
|
||||
[1, configCase.m428],
|
||||
[2, configCase.m429],
|
||||
...(configCase.m430 >= 0 ? [[3, configCase.m430]] : []),
|
||||
]) {
|
||||
const sawSwitch = configEvents.some(
|
||||
(event) => event.type === "kinematics-switch" && event.line === line && event.reserved === kinstype,
|
||||
);
|
||||
if (!sawSwitch) {
|
||||
throw new Error(
|
||||
`missing LinuxCNC WASM browser generated ${configCase.field}=${configCase.value} line ${line} switchkins type ${kinstype}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
result.textContent = "browser wasm smoke passed";
|
||||
} finally {
|
||||
simulator.dispose();
|
||||
}
|
||||
} catch (error) {
|
||||
result.textContent = `browser wasm smoke failed: ${error && error.stack ? error.stack : error}`;
|
||||
}
|
||||
runBrowserWasmSmoke({
|
||||
createWasmSimulator,
|
||||
result: document.querySelector("#result"),
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user