提交 xyzbc-trt 界面与验证更新
This commit is contained in:
4573
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/linuxcnc-hal.js
vendored
Normal file
4573
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/linuxcnc-hal.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
228
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/linuxcnc-interp.js
vendored
Normal file
228
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/linuxcnc-interp.js
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
import createLinuxCncInterpModule from "../../../build/wasm/core/linuxcnc_interp.js";
|
||||
import { createVirtualHalWasmBridgeSnapshot } from "./linuxcnc-hal.js";
|
||||
|
||||
function allocCString(mod, value) {
|
||||
const bytes = mod.lengthBytesUTF8(value) + 1;
|
||||
const ptr = mod._malloc(bytes);
|
||||
mod.stringToUTF8(value, ptr, bytes);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function ensureParentPath(mod, path) {
|
||||
const parts = path.split("/").filter(Boolean);
|
||||
let current = "";
|
||||
for (const part of parts.slice(0, -1)) {
|
||||
current += `/${part}`;
|
||||
try {
|
||||
mod.FS.mkdir(current);
|
||||
} catch {
|
||||
// Directory already exists.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyResultString(mod, resultPtr, functionName) {
|
||||
if (!resultPtr) {
|
||||
throw new Error(`${functionName} returned null`);
|
||||
}
|
||||
return mod.UTF8ToString(resultPtr);
|
||||
}
|
||||
|
||||
function callStringResult(mod, functionName, ...values) {
|
||||
const ptrs = values.map((value) => allocCString(mod, value));
|
||||
let resultPtr = 0;
|
||||
try {
|
||||
resultPtr = mod[`_${functionName}`](...ptrs);
|
||||
return copyResultString(mod, resultPtr, functionName);
|
||||
} finally {
|
||||
if (resultPtr) {
|
||||
mod._lcinterp_free_string(resultPtr);
|
||||
}
|
||||
for (const ptr of ptrs) {
|
||||
mod._free(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function requireWasmFunction(mod, functionName) {
|
||||
const fn = mod[`_${functionName}`];
|
||||
if (typeof fn !== "function") {
|
||||
throw new Error(`linuxcnc interpreter WASM missing ${functionName}; rebuild wasm-port/tools/build_wasm_core.sh`);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
function callVoidWithStringsAndNumbers(mod, functionName, strings, numbers = []) {
|
||||
const fn = requireWasmFunction(mod, functionName);
|
||||
const ptrs = strings.map((value) => allocCString(mod, value));
|
||||
try {
|
||||
return fn(...ptrs, ...numbers);
|
||||
} finally {
|
||||
for (const ptr of ptrs) {
|
||||
mod._free(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function createLinuxCncInterpSdk(moduleOptions = {}) {
|
||||
const mod = await createLinuxCncInterpModule(moduleOptions);
|
||||
|
||||
return {
|
||||
module: mod,
|
||||
|
||||
hasWasmFunction(functionName) {
|
||||
return typeof mod[`_${functionName}`] === "function";
|
||||
},
|
||||
|
||||
writeTextFile(path, text) {
|
||||
ensureParentPath(mod, path);
|
||||
mod.FS.writeFile(path, text, { encoding: "utf8" });
|
||||
},
|
||||
|
||||
readTextFile(path) {
|
||||
return mod.FS.readFile(path, { encoding: "utf8" });
|
||||
},
|
||||
|
||||
runSimConfigProgram({
|
||||
iniPath,
|
||||
programPath,
|
||||
files = [],
|
||||
executionMode = "fileWithIni",
|
||||
}) {
|
||||
for (const file of files) {
|
||||
ensureParentPath(mod, file.path);
|
||||
mod.FS.writeFile(file.path, file.text, { encoding: "utf8" });
|
||||
if (file.executable) {
|
||||
mod.FS.chmod(file.path, 0o755);
|
||||
}
|
||||
}
|
||||
|
||||
if (executionMode === "fiveAxisRemap") {
|
||||
return callStringResult(
|
||||
mod,
|
||||
"lcinterp_run_fiveaxis_remap_file",
|
||||
programPath,
|
||||
iniPath,
|
||||
);
|
||||
}
|
||||
if (executionMode === "fileWithIni") {
|
||||
return callStringResult(mod, "lcinterp_run_file_with_ini", programPath, iniPath);
|
||||
}
|
||||
throw new Error(`unsupported sim config execution mode: ${executionMode}`);
|
||||
},
|
||||
|
||||
runProgram(programText) {
|
||||
return callStringResult(mod, "lcinterp_run_program", programText);
|
||||
},
|
||||
|
||||
runProgramWithIni(programText, iniPath) {
|
||||
return callStringResult(mod, "lcinterp_run_program_with_ini", programText, iniPath);
|
||||
},
|
||||
|
||||
runFile(path) {
|
||||
return callStringResult(mod, "lcinterp_run_file", path);
|
||||
},
|
||||
|
||||
runFileWithIni(path, iniPath) {
|
||||
return callStringResult(mod, "lcinterp_run_file_with_ini", path, iniPath);
|
||||
},
|
||||
|
||||
runFileWithIniContinueOnError(path, iniPath) {
|
||||
return callStringResult(
|
||||
mod,
|
||||
"lcinterp_run_file_with_ini_continue_on_error",
|
||||
path,
|
||||
iniPath,
|
||||
);
|
||||
},
|
||||
|
||||
runFiveAxisRemapFile(path, iniPath) {
|
||||
return callStringResult(mod, "lcinterp_run_fiveaxis_remap_file", path, iniPath);
|
||||
},
|
||||
|
||||
runRemapFile(path, iniPath) {
|
||||
return callStringResult(mod, "lcinterp_run_remap_file", path, iniPath);
|
||||
},
|
||||
|
||||
runRemapFileContinueOnError(path, iniPath) {
|
||||
return callStringResult(mod, "lcinterp_run_remap_file_continue_on_error", path, iniPath);
|
||||
},
|
||||
|
||||
runRemapIoMdiSequence(iniPath) {
|
||||
return callStringResult(mod, "lcinterp_run_remap_io_mdi_sequence", iniPath);
|
||||
},
|
||||
|
||||
restoreParameters(path) {
|
||||
return callStringResult(mod, "lcinterp_restore_parameters", path);
|
||||
},
|
||||
|
||||
saveParameters(path, values) {
|
||||
const assignments = Object.entries(values)
|
||||
.map(([parameter, value]) => `${parameter} ${value}`)
|
||||
.join("\n");
|
||||
return callStringResult(mod, "lcinterp_save_parameters", path, assignments);
|
||||
},
|
||||
|
||||
loadToolTable(path, options = {}) {
|
||||
const functionName = options.randomToolChanger
|
||||
? "lcinterp_load_tool_table_random"
|
||||
: "lcinterp_load_tool_table";
|
||||
return callStringResult(mod, functionName, path);
|
||||
},
|
||||
|
||||
saveToolTable(path) {
|
||||
return callStringResult(mod, "lcinterp_save_tool_table", path);
|
||||
},
|
||||
|
||||
probeInitAndSynch() {
|
||||
return callStringResult(mod, "lcinterp_probe_init_and_synch");
|
||||
},
|
||||
|
||||
probeIndexer() {
|
||||
return callStringResult(mod, "lcinterp_probe_indexer");
|
||||
},
|
||||
|
||||
probeNamedParameters(iniPath) {
|
||||
return callStringResult(mod, "lcinterp_probe_named_parameters", iniPath);
|
||||
},
|
||||
|
||||
resetHal() {
|
||||
requireWasmFunction(mod, "lcinterp_hal_reset")();
|
||||
},
|
||||
|
||||
setHalValue({ kind = "pin", name, type = "HAL_FLOAT", value = 0, connected = true }) {
|
||||
if (!name) {
|
||||
throw new Error("setHalValue requires a HAL name");
|
||||
}
|
||||
return callVoidWithStringsAndNumbers(
|
||||
mod,
|
||||
"lcinterp_hal_set_value",
|
||||
[kind, name, type],
|
||||
[Number(value) || 0, connected === false ? 0 : 1],
|
||||
);
|
||||
},
|
||||
|
||||
applyVirtualHalSnapshot(snapshot, options = {}) {
|
||||
if (options.reset !== false) {
|
||||
this.resetHal();
|
||||
}
|
||||
for (const value of snapshot?.values ?? []) {
|
||||
this.setHalValue(value);
|
||||
}
|
||||
return {
|
||||
apiName: "linuxcnc-wasm-hal-apply-result",
|
||||
applied: snapshot?.values?.length ?? 0,
|
||||
reset: options.reset !== false,
|
||||
source: snapshot?.source ?? "unknown",
|
||||
};
|
||||
},
|
||||
|
||||
applyVirtualHalState(halState, options = {}) {
|
||||
return this.applyVirtualHalSnapshot(createVirtualHalWasmBridgeSnapshot(halState), options);
|
||||
},
|
||||
|
||||
probeHalNamed(name) {
|
||||
return callStringResult(mod, "lcinterp_probe_hal_named", name);
|
||||
},
|
||||
};
|
||||
}
|
||||
206
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/linuxcnc-kinematics.js
vendored
Normal file
206
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/linuxcnc-kinematics.js
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
import createLinuxCncTrivkinsKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_trivkins_kinematics.js";
|
||||
import createLinuxCnc5axiskinsKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_5axiskins_kinematics.js";
|
||||
import createLinuxCncXyzacTrtKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_xyzac_trt_kinematics.js";
|
||||
import createLinuxCncXyzbcTrtKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_xyzbc_trt_kinematics.js";
|
||||
import createLinuxCncCorexyKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_corexy_kinematics.js";
|
||||
import createLinuxCncRotateKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_rotate_kinematics.js";
|
||||
import createLinuxCncRoseKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_rose_kinematics.js";
|
||||
import createLinuxCncMaxKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_max_kinematics.js";
|
||||
import createLinuxCncLineardeltaKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_lineardelta_kinematics.js";
|
||||
import createLinuxCncRotarydeltaKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_rotarydelta_kinematics.js";
|
||||
import createLinuxCncScorbotKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_scorbot_kinematics.js";
|
||||
import createLinuxCncTripodKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_tripod_kinematics.js";
|
||||
import createLinuxCncScaraKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_scara_kinematics.js";
|
||||
import createLinuxCncPumaKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_puma_kinematics.js";
|
||||
import createLinuxCncGenserKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_genser_kinematics.js";
|
||||
import createLinuxCncGenhexKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_genhex_kinematics.js";
|
||||
import createLinuxCncPentakinsKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_pentakins_kinematics.js";
|
||||
|
||||
export const LINUXCNC_KINEMATICS_MODULES = [
|
||||
{ id: "trivkins", wasmFile: "linuxcnc_trivkins_kinematics.wasm", factory: createLinuxCncTrivkinsKinematicsModule },
|
||||
{ id: "5axiskins", wasmFile: "linuxcnc_5axiskins_kinematics.wasm", factory: createLinuxCnc5axiskinsKinematicsModule },
|
||||
{ id: "xyzac-trt", wasmFile: "linuxcnc_xyzac_trt_kinematics.wasm", factory: createLinuxCncXyzacTrtKinematicsModule },
|
||||
{ id: "xyzbc-trt", wasmFile: "linuxcnc_xyzbc_trt_kinematics.wasm", factory: createLinuxCncXyzbcTrtKinematicsModule },
|
||||
{ id: "corexy", wasmFile: "linuxcnc_corexy_kinematics.wasm", factory: createLinuxCncCorexyKinematicsModule },
|
||||
{ id: "rotate", wasmFile: "linuxcnc_rotate_kinematics.wasm", factory: createLinuxCncRotateKinematicsModule },
|
||||
{ id: "rose", wasmFile: "linuxcnc_rose_kinematics.wasm", factory: createLinuxCncRoseKinematicsModule },
|
||||
{ id: "max", wasmFile: "linuxcnc_max_kinematics.wasm", factory: createLinuxCncMaxKinematicsModule },
|
||||
{ id: "lineardelta", wasmFile: "linuxcnc_lineardelta_kinematics.wasm", factory: createLinuxCncLineardeltaKinematicsModule },
|
||||
{ id: "rotarydelta", wasmFile: "linuxcnc_rotarydelta_kinematics.wasm", factory: createLinuxCncRotarydeltaKinematicsModule },
|
||||
{ id: "scorbot", wasmFile: "linuxcnc_scorbot_kinematics.wasm", factory: createLinuxCncScorbotKinematicsModule },
|
||||
{ id: "tripod", wasmFile: "linuxcnc_tripod_kinematics.wasm", factory: createLinuxCncTripodKinematicsModule },
|
||||
{ id: "scara", wasmFile: "linuxcnc_scara_kinematics.wasm", factory: createLinuxCncScaraKinematicsModule },
|
||||
{ id: "puma", wasmFile: "linuxcnc_puma_kinematics.wasm", factory: createLinuxCncPumaKinematicsModule },
|
||||
{ id: "genser", wasmFile: "linuxcnc_genser_kinematics.wasm", factory: createLinuxCncGenserKinematicsModule },
|
||||
{ id: "genhex", wasmFile: "linuxcnc_genhex_kinematics.wasm", factory: createLinuxCncGenhexKinematicsModule },
|
||||
{ id: "pentakins", wasmFile: "linuxcnc_pentakins_kinematics.wasm", factory: createLinuxCncPentakinsKinematicsModule },
|
||||
];
|
||||
|
||||
const MODULES_BY_ID = new Map(LINUXCNC_KINEMATICS_MODULES.map((entry) => [entry.id, entry]));
|
||||
|
||||
function requireWasmFunction(mod, functionName) {
|
||||
const fn = mod[`_${functionName}`];
|
||||
if (typeof fn !== "function") {
|
||||
throw new Error(`linuxcnc kinematics WASM missing ${functionName}; rebuild wasm-port/tools/build_kinematics_wasm.sh`);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
function writeDoubleArray(mod, values) {
|
||||
const bytes = values.length * 8;
|
||||
const ptr = mod._malloc(bytes);
|
||||
mod.HEAPF64.set(values, ptr / 8);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function readDoubleArray(mod, ptr, length) {
|
||||
return Array.from(mod.HEAPF64.subarray(ptr / 8, ptr / 8 + length));
|
||||
}
|
||||
|
||||
function poseValuesFromObject(pose = {}) {
|
||||
return [
|
||||
Number(pose.x ?? pose.tran?.x ?? 0),
|
||||
Number(pose.y ?? pose.tran?.y ?? 0),
|
||||
Number(pose.z ?? pose.tran?.z ?? 0),
|
||||
Number(pose.a ?? 0),
|
||||
Number(pose.b ?? 0),
|
||||
Number(pose.c ?? 0),
|
||||
Number(pose.u ?? 0),
|
||||
Number(pose.v ?? 0),
|
||||
Number(pose.w ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
function poseObjectFromValues(values) {
|
||||
return {
|
||||
x: values[0],
|
||||
y: values[1],
|
||||
z: values[2],
|
||||
a: values[3],
|
||||
b: values[4],
|
||||
c: values[5],
|
||||
u: values[6],
|
||||
v: values[7],
|
||||
w: values[8],
|
||||
};
|
||||
}
|
||||
|
||||
export function supportedLinuxCncKinematicsModules() {
|
||||
return LINUXCNC_KINEMATICS_MODULES.map((entry) => entry.id);
|
||||
}
|
||||
|
||||
export function linuxCncKinematicsWasmFile(moduleId) {
|
||||
return MODULES_BY_ID.get(moduleId)?.wasmFile || null;
|
||||
}
|
||||
|
||||
export async function createLinuxCncKinematicsSdk({
|
||||
moduleId = "xyzac-trt",
|
||||
moduleOptions = {},
|
||||
} = {}) {
|
||||
const entry = MODULES_BY_ID.get(moduleId);
|
||||
if (!entry) {
|
||||
throw new Error(`unsupported LinuxCNC kinematics module: ${moduleId}`);
|
||||
}
|
||||
|
||||
const mod = await entry.factory(moduleOptions);
|
||||
requireWasmFunction(mod, "lckins_init")();
|
||||
|
||||
return {
|
||||
apiName: "linuxcnc-kinematics-wasm-sdk",
|
||||
moduleId,
|
||||
module: mod,
|
||||
wasmFile: entry.wasmFile,
|
||||
|
||||
hasWasmFunction(functionName) {
|
||||
return typeof mod[`_${functionName}`] === "function";
|
||||
},
|
||||
|
||||
type() {
|
||||
return requireWasmFunction(mod, "lckins_type")();
|
||||
},
|
||||
|
||||
switchable() {
|
||||
return requireWasmFunction(mod, "lckins_switchable")();
|
||||
},
|
||||
|
||||
switchKinematics(switchkinsType) {
|
||||
return requireWasmFunction(mod, "lckins_switch")(Number(switchkinsType) || 0);
|
||||
},
|
||||
|
||||
forward(joints, options = {}) {
|
||||
const jointValues = Array.from(joints, Number);
|
||||
const jointsPtr = writeDoubleArray(mod, jointValues);
|
||||
const posePtr = writeDoubleArray(mod, poseValuesFromObject(options.seedPose));
|
||||
const fflagsPtr = mod._malloc(8);
|
||||
const iflagsPtr = mod._malloc(8);
|
||||
mod.HEAPU32[fflagsPtr / 4] = 0;
|
||||
mod.HEAPU32[iflagsPtr / 4] = 0;
|
||||
try {
|
||||
const rc = requireWasmFunction(mod, "lckins_forward")(
|
||||
jointsPtr,
|
||||
jointValues.length,
|
||||
posePtr,
|
||||
fflagsPtr,
|
||||
iflagsPtr,
|
||||
);
|
||||
return {
|
||||
rc,
|
||||
pose: poseObjectFromValues(readDoubleArray(mod, posePtr, 9)),
|
||||
fflags: mod.HEAPU32[fflagsPtr / 4],
|
||||
iflags: mod.HEAPU32[iflagsPtr / 4],
|
||||
};
|
||||
} finally {
|
||||
mod._free(jointsPtr);
|
||||
mod._free(posePtr);
|
||||
mod._free(fflagsPtr);
|
||||
mod._free(iflagsPtr);
|
||||
}
|
||||
},
|
||||
|
||||
inverse(pose, jointCount = 5, options = {}) {
|
||||
const posePtr = writeDoubleArray(mod, poseValuesFromObject(pose));
|
||||
const jointsPtr = mod._malloc(jointCount * 8);
|
||||
const iflagsPtr = mod._malloc(8);
|
||||
const fflagsPtr = mod._malloc(8);
|
||||
const seedJoints = Array.isArray(options.seedJoints)
|
||||
? options.seedJoints.map(Number)
|
||||
: [];
|
||||
mod.HEAPF64.fill(0, jointsPtr / 8, jointsPtr / 8 + jointCount);
|
||||
mod.HEAPF64.set(seedJoints.slice(0, jointCount), jointsPtr / 8);
|
||||
mod.HEAPU32[iflagsPtr / 4] = 0;
|
||||
mod.HEAPU32[fflagsPtr / 4] = 0;
|
||||
try {
|
||||
const rc = requireWasmFunction(mod, "lckins_inverse")(
|
||||
posePtr,
|
||||
jointsPtr,
|
||||
jointCount,
|
||||
iflagsPtr,
|
||||
fflagsPtr,
|
||||
);
|
||||
return {
|
||||
rc,
|
||||
joints: readDoubleArray(mod, jointsPtr, jointCount),
|
||||
iflags: mod.HEAPU32[iflagsPtr / 4],
|
||||
fflags: mod.HEAPU32[fflagsPtr / 4],
|
||||
};
|
||||
} finally {
|
||||
mod._free(posePtr);
|
||||
mod._free(jointsPtr);
|
||||
mod._free(iflagsPtr);
|
||||
mod._free(fflagsPtr);
|
||||
}
|
||||
},
|
||||
|
||||
runProbe() {
|
||||
const resultPtr = requireWasmFunction(mod, "lckins_run_probe")();
|
||||
if (!resultPtr) {
|
||||
throw new Error("lckins_run_probe returned null");
|
||||
}
|
||||
try {
|
||||
return mod.UTF8ToString(resultPtr);
|
||||
} finally {
|
||||
requireWasmFunction(mod, "lckins_free_string")(resultPtr);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
132
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/linuxcnc-task-hal.js
vendored
Normal file
132
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/linuxcnc-task-hal.js
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
import createLinuxCncTaskHalModule from "../../../build/wasm/task-hal/linuxcnc_task_hal.js";
|
||||
|
||||
const SEMANTIC_BOUNDARY = "linuxcnc_task_motion_hal_wasm_phase4_minimal";
|
||||
|
||||
function allocCString(mod, value) {
|
||||
const text = String(value ?? "");
|
||||
const bytes = mod.lengthBytesUTF8(text) + 1;
|
||||
const ptr = mod._malloc(bytes);
|
||||
mod.stringToUTF8(text, ptr, bytes);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function withCString(mod, value, fn) {
|
||||
const ptr = allocCString(mod, value);
|
||||
try {
|
||||
return fn(ptr);
|
||||
} finally {
|
||||
mod._free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
function requireWasmFunction(mod, functionName) {
|
||||
const fn = mod[`_${functionName}`];
|
||||
if (typeof fn !== "function") {
|
||||
throw new Error(`linuxcnc task/HAL WASM missing ${functionName}; rebuild wasm-port/tools/build_task_hal_wasm.sh`);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
function readJson(mod, functionName) {
|
||||
const fn = requireWasmFunction(mod, functionName);
|
||||
let bytes = 131072;
|
||||
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||
const ptr = mod._malloc(bytes);
|
||||
try {
|
||||
const rc = fn(ptr, bytes);
|
||||
if (rc === 0) {
|
||||
return JSON.parse(mod.UTF8ToString(ptr));
|
||||
}
|
||||
if (rc > bytes) {
|
||||
bytes = rc;
|
||||
continue;
|
||||
}
|
||||
throw new Error(`${functionName} failed with rc=${rc}`);
|
||||
} finally {
|
||||
mod._free(ptr);
|
||||
}
|
||||
}
|
||||
throw new Error(`${functionName} output exceeded buffer`);
|
||||
}
|
||||
|
||||
function callWithJson(mod, functionName, payload) {
|
||||
const fn = requireWasmFunction(mod, functionName);
|
||||
return withCString(mod, JSON.stringify(payload ?? {}), (ptr) => fn(ptr));
|
||||
}
|
||||
|
||||
export async function createLinuxCncTaskHalSdk(moduleOptions = {}) {
|
||||
const mod = await createLinuxCncTaskHalModule(moduleOptions);
|
||||
|
||||
return {
|
||||
apiName: "linuxcnc-task-hal-wasm-sdk",
|
||||
semanticBoundary: SEMANTIC_BOUNDARY,
|
||||
module: mod,
|
||||
|
||||
readiness() {
|
||||
return {
|
||||
apiName: "linuxcnc-task-hal-wasm-sdk-readiness",
|
||||
loaded: true,
|
||||
semanticBoundary: SEMANTIC_BOUNDARY,
|
||||
taskRuntimeReady: typeof mod._lctask_init_session === "function",
|
||||
motionRuntimeReady: typeof mod._lcmot_step_servo === "function",
|
||||
halRuntimeReady: typeof mod._lchal_get_snapshot_json === "function",
|
||||
nativeTaskReady: false,
|
||||
nativeHalSyncReady: false,
|
||||
};
|
||||
},
|
||||
|
||||
initSession(session = {}) {
|
||||
const rc = callWithJson(mod, "lctask_init_session", session);
|
||||
if (rc !== 0) {
|
||||
throw new Error(`lctask_init_session failed with rc=${rc}`);
|
||||
}
|
||||
return rc;
|
||||
},
|
||||
|
||||
stageFile(path, text) {
|
||||
return withCString(mod, path, (pathPtr) =>
|
||||
withCString(mod, text, (textPtr) =>
|
||||
requireWasmFunction(mod, "lctask_stage_file")(pathPtr, textPtr),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
openProgram(path) {
|
||||
return withCString(mod, path, (pathPtr) =>
|
||||
requireWasmFunction(mod, "lctask_open_program")(pathPtr),
|
||||
);
|
||||
},
|
||||
|
||||
loadProgramMotionPlan(plan) {
|
||||
return callWithJson(mod, "lctask_load_program_motion_plan_json", plan);
|
||||
},
|
||||
|
||||
sendCommand(command) {
|
||||
return callWithJson(mod, "lctask_send_command_json", command);
|
||||
},
|
||||
|
||||
runCycles({
|
||||
taskPeriodNs = 10000000,
|
||||
servoPeriodNs = 1000000,
|
||||
taskCycles = 1,
|
||||
} = {}) {
|
||||
return requireWasmFunction(mod, "lctask_run_cycles")(
|
||||
Number(taskPeriodNs) || 10000000,
|
||||
Number(servoPeriodNs) || 1000000,
|
||||
Number(taskCycles) || 0,
|
||||
);
|
||||
},
|
||||
|
||||
readStatus() {
|
||||
return readJson(mod, "lctask_read_status_json");
|
||||
},
|
||||
|
||||
readEvents() {
|
||||
return readJson(mod, "lctask_read_events_json");
|
||||
},
|
||||
|
||||
resetSession() {
|
||||
return requireWasmFunction(mod, "lctask_reset_session")();
|
||||
},
|
||||
};
|
||||
}
|
||||
228
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/linuxcnc-tp.js
vendored
Normal file
228
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/linuxcnc-tp.js
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
import createLinuxCncTpModule from "../../../build/wasm/tp/linuxcnc_tp.js";
|
||||
|
||||
const SEMANTIC_BOUNDARY = "linuxcnc_tp_queue_runtime_timing_from_canonical_motion";
|
||||
|
||||
function allocCString(mod, value) {
|
||||
const bytes = mod.lengthBytesUTF8(value) + 1;
|
||||
const ptr = mod._malloc(bytes);
|
||||
mod.stringToUTF8(value, ptr, bytes);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function callTiming(mod, payload) {
|
||||
const inputPtr = allocCString(mod, JSON.stringify(payload));
|
||||
let resultPtr = 0;
|
||||
try {
|
||||
resultPtr = mod._lctp_run_canonical_motion_timing(inputPtr);
|
||||
if (!resultPtr) {
|
||||
throw new Error("lctp_run_canonical_motion_timing returned null");
|
||||
}
|
||||
const text = mod.UTF8ToString(resultPtr);
|
||||
const result = JSON.parse(text);
|
||||
if (result.ok !== true) {
|
||||
throw new Error(result.error || "LinuxCNC TP queue timing failed");
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
if (resultPtr) mod._lctp_free_string(resultPtr);
|
||||
mod._free(inputPtr);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createLinuxCncTpSdk(moduleOptions = {}) {
|
||||
const mod = await createLinuxCncTpModule(moduleOptions);
|
||||
|
||||
return {
|
||||
apiName: "linuxcnc-tp-wasm-sdk",
|
||||
semanticBoundary: SEMANTIC_BOUNDARY,
|
||||
module: mod,
|
||||
|
||||
readiness() {
|
||||
return {
|
||||
apiName: "linuxcnc-tp-wasm-sdk-readiness",
|
||||
loaded: true,
|
||||
semanticBoundary: SEMANTIC_BOUNDARY,
|
||||
runCanonicalMotionTimingReady: typeof mod._lctp_run_canonical_motion_timing === "function",
|
||||
};
|
||||
},
|
||||
|
||||
runCanonicalMotionTiming({ motion = [], options = {} } = {}) {
|
||||
const result = callTiming(mod, {
|
||||
...options,
|
||||
motion: motion.map((event, index) => ({
|
||||
index,
|
||||
line: Number.isFinite(Number(event.line)) ? Number(event.line) : -1,
|
||||
type: event.type,
|
||||
feedRate: Number.isFinite(Number(event.feedRate)) ? Number(event.feedRate) : 0,
|
||||
x: numberOrZero(event.axes?.x),
|
||||
y: numberOrZero(event.axes?.y),
|
||||
z: numberOrZero(event.axes?.z),
|
||||
a: numberOrZero(event.axes?.a),
|
||||
b: numberOrZero(event.axes?.b),
|
||||
c: numberOrZero(event.axes?.c),
|
||||
u: numberOrZero(event.axes?.u),
|
||||
v: numberOrZero(event.axes?.v),
|
||||
w: numberOrZero(event.axes?.w),
|
||||
plane: numberOrZero(event.axes?.arc?.plane),
|
||||
centerFirst: numberOrZero(event.axes?.arc?.centerFirst),
|
||||
centerSecond: numberOrZero(event.axes?.arc?.centerSecond),
|
||||
rotation: numberOrZero(event.axes?.arc?.rotation),
|
||||
axisEndPoint: numberOrZero(event.axes?.arc?.axisEndPoint),
|
||||
})),
|
||||
});
|
||||
return normalizeTimingResult(result, motion);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTimingResult(result, motion) {
|
||||
const rawSegmentsByIndex = new Map((result.segments || []).map((segment) => [segment.index, segment]));
|
||||
let elapsedSeconds = 0;
|
||||
const segments = motion.map((event, index) => {
|
||||
const raw = rawSegmentsByIndex.get(index);
|
||||
const durationSeconds = Math.max(Number(raw?.durationSeconds) || 0, 0);
|
||||
elapsedSeconds += durationSeconds;
|
||||
return {
|
||||
index,
|
||||
line: event.line ?? null,
|
||||
type: event.type,
|
||||
motionClass: event.type === "STRAIGHT_TRAVERSE" ? "rapid" : "feed",
|
||||
durationSeconds,
|
||||
elapsedSeconds,
|
||||
currentVelocity: Number(raw?.velocity) || 0,
|
||||
velocityMmPerMin: (Number(raw?.velocity) || 0) * 60,
|
||||
queueDepth: Number(raw?.queueDepth) || 0,
|
||||
activeDepth: Number(raw?.activeDepth) || 0,
|
||||
cycles: Number(raw?.cycles) || 0,
|
||||
axes: event.axes || {},
|
||||
runtimeAxes: axesFromRuntimeRecord(raw, event.axes || {}),
|
||||
tp: raw || null,
|
||||
};
|
||||
});
|
||||
const rawSamples = Array.isArray(result.samples) && result.samples.length > 0
|
||||
? result.samples
|
||||
: synthesizeSamplesFromSegments(result.segments || []);
|
||||
const samples = normalizeSamples(rawSamples);
|
||||
|
||||
const feedSeconds = segments
|
||||
.filter((segment) => segment.motionClass === "feed")
|
||||
.reduce((total, segment) => total + segment.durationSeconds, 0);
|
||||
const rapidSeconds = segments
|
||||
.filter((segment) => segment.motionClass === "rapid")
|
||||
.reduce((total, segment) => total + segment.durationSeconds, 0);
|
||||
|
||||
return {
|
||||
apiName: "web-rtcp-5axis-linuxcnc-tp-program-timing",
|
||||
semanticBoundary: SEMANTIC_BOUNDARY,
|
||||
sourceBasis: "LinuxCNC interpreter canonical motion events queued through LinuxCNC src/emc/tp WASM",
|
||||
plannerRuntimeReady: result.ok === true
|
||||
&& result.addFailures === 0
|
||||
&& result.tpDone === 1
|
||||
&& segments.length === motion.length,
|
||||
sampleCount: Number(result.sampleCount) || samples.length,
|
||||
samples,
|
||||
totalSeconds: elapsedSeconds,
|
||||
totalMinutes: elapsedSeconds / 60,
|
||||
feedSeconds,
|
||||
rapidSeconds,
|
||||
motionCount: motion.length,
|
||||
emittedMotionCount: result.emittedMotionCount || 0,
|
||||
acceptedMotionCount: result.acceptedMotionCount || 0,
|
||||
totalCycles: result.totalCycles || 0,
|
||||
cycleTime: result.cycleTime || 0.001,
|
||||
addFailures: result.addFailures || 0,
|
||||
tpDone: result.tpDone === 1,
|
||||
finalQueueDepth: result.finalQueueDepth || 0,
|
||||
segments,
|
||||
raw: result,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSamples(samples) {
|
||||
return samples.map((sample, index) => ({
|
||||
sampleIndex: Number(sample.sampleIndex ?? index),
|
||||
cycle: Number(sample.cycle) || 0,
|
||||
timeSeconds: Number(sample.timeSeconds) || 0,
|
||||
motionIndex: Number(sample.motionIndex) || 0,
|
||||
line: Number.isFinite(Number(sample.line)) ? Number(sample.line) : null,
|
||||
type: sample.type || "-",
|
||||
axes: {
|
||||
x: numberOrZero(sample.x),
|
||||
y: numberOrZero(sample.y),
|
||||
z: numberOrZero(sample.z),
|
||||
a: numberOrZero(sample.a),
|
||||
b: numberOrZero(sample.b),
|
||||
c: numberOrZero(sample.c),
|
||||
u: numberOrZero(sample.u),
|
||||
v: numberOrZero(sample.v),
|
||||
w: numberOrZero(sample.w),
|
||||
},
|
||||
currentVelocity: numberOrZero(sample.currentVelocity),
|
||||
currentVelocityMmPerMin: numberOrZero(sample.currentVelocity) * 60,
|
||||
requestedVelocity: numberOrZero(sample.requestedVelocity),
|
||||
requestedVelocityMmPerMin: numberOrZero(sample.requestedVelocity) * 60,
|
||||
distanceToGo: numberOrZero(sample.distanceToGo),
|
||||
dtg: {
|
||||
x: numberOrZero(sample.dtgX),
|
||||
y: numberOrZero(sample.dtgY),
|
||||
z: numberOrZero(sample.dtgZ),
|
||||
},
|
||||
queueDepth: Number(sample.queueDepth) || 0,
|
||||
activeDepth: Number(sample.activeDepth) || 0,
|
||||
}));
|
||||
}
|
||||
|
||||
function synthesizeSamplesFromSegments(segments) {
|
||||
return segments.map((segment, index) => ({
|
||||
sampleIndex: index,
|
||||
cycle: Number(segment.cycles) || 0,
|
||||
timeSeconds: Number(segment.elapsedSeconds) || 0,
|
||||
motionIndex: Number(segment.index) || index,
|
||||
line: segment.line,
|
||||
type: segment.type,
|
||||
x: segment.x,
|
||||
y: segment.y,
|
||||
z: segment.z,
|
||||
a: segment.a,
|
||||
b: segment.b,
|
||||
c: segment.c,
|
||||
u: segment.u,
|
||||
v: segment.v,
|
||||
w: segment.w,
|
||||
currentVelocity: segment.velocity,
|
||||
requestedVelocity: segment.velocity,
|
||||
distanceToGo: segment.distanceToGo ?? 0,
|
||||
dtgX: segment.dtgX ?? 0,
|
||||
dtgY: segment.dtgY ?? 0,
|
||||
dtgZ: segment.dtgZ ?? 0,
|
||||
queueDepth: segment.queueDepth,
|
||||
activeDepth: segment.activeDepth,
|
||||
}));
|
||||
}
|
||||
|
||||
function axesFromRuntimeRecord(record, fallback = {}) {
|
||||
return {
|
||||
x: numberOrFallback(record?.x, fallback.x, 0),
|
||||
y: numberOrFallback(record?.y, fallback.y, 0),
|
||||
z: numberOrFallback(record?.z, fallback.z, 0),
|
||||
a: numberOrFallback(record?.a, fallback.a, 0),
|
||||
b: numberOrFallback(record?.b, fallback.b, 0),
|
||||
c: numberOrFallback(record?.c, fallback.c, 0),
|
||||
u: numberOrFallback(record?.u, fallback.u, 0),
|
||||
v: numberOrFallback(record?.v, fallback.v, 0),
|
||||
w: numberOrFallback(record?.w, fallback.w, 0),
|
||||
};
|
||||
}
|
||||
|
||||
function numberOrFallback(...values) {
|
||||
for (const value of values) {
|
||||
const number = Number(value);
|
||||
if (Number.isFinite(number)) return number;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function numberOrZero(value) {
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? number : 0;
|
||||
}
|
||||
486
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/sim-config-staging.js
vendored
Normal file
486
web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/wasm-port/runtime/sdk/src/sim-config-staging.js
vendored
Normal file
@@ -0,0 +1,486 @@
|
||||
function cleanManifest(manifestText) {
|
||||
return manifestText
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && !line.startsWith("#"));
|
||||
}
|
||||
|
||||
function normalizeRel(path) {
|
||||
const parts = [];
|
||||
for (const part of path.split("/")) {
|
||||
if (!part || part === ".") {
|
||||
continue;
|
||||
}
|
||||
if (part === "..") {
|
||||
parts.pop();
|
||||
continue;
|
||||
}
|
||||
parts.push(part);
|
||||
}
|
||||
return parts.join("/");
|
||||
}
|
||||
|
||||
function dirname(path) {
|
||||
const clean = normalizeRel(path);
|
||||
const index = clean.lastIndexOf("/");
|
||||
return index === -1 ? "" : clean.slice(0, index);
|
||||
}
|
||||
|
||||
function basename(path) {
|
||||
const clean = normalizeRel(path);
|
||||
const index = clean.lastIndexOf("/");
|
||||
return index === -1 ? clean : clean.slice(index + 1);
|
||||
}
|
||||
|
||||
function stripIniComment(line) {
|
||||
const hash = line.indexOf("#");
|
||||
const semicolon = line.indexOf(";");
|
||||
const indexes = [hash, semicolon].filter((index) => index >= 0);
|
||||
if (indexes.length === 0) {
|
||||
return line;
|
||||
}
|
||||
return line.slice(0, Math.min(...indexes));
|
||||
}
|
||||
|
||||
function parseIni(iniText) {
|
||||
const values = new Map();
|
||||
let section = "";
|
||||
for (const rawLine of iniText.split("\n")) {
|
||||
const sectionMatch = rawLine.match(/^\s*\[([^\]]+)\]/);
|
||||
if (sectionMatch) {
|
||||
section = sectionMatch[1].trim().toUpperCase();
|
||||
continue;
|
||||
}
|
||||
|
||||
const line = stripIniComment(rawLine);
|
||||
const equals = line.indexOf("=");
|
||||
if (equals === -1 || !section) {
|
||||
continue;
|
||||
}
|
||||
const key = line.slice(0, equals).trim().toUpperCase();
|
||||
const value = line.slice(equals + 1).trim();
|
||||
if (!key || !value) {
|
||||
continue;
|
||||
}
|
||||
const mapKey = `${section}.${key}`;
|
||||
const existing = values.get(mapKey) ?? [];
|
||||
existing.push(value);
|
||||
values.set(mapKey, existing);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function firstIniValue(values, section, key) {
|
||||
return values.get(`${section.toUpperCase()}.${key.toUpperCase()}`)?.[0] ?? null;
|
||||
}
|
||||
|
||||
function allIniValues(values, section, key) {
|
||||
return values.get(`${section.toUpperCase()}.${key.toUpperCase()}`) ?? [];
|
||||
}
|
||||
|
||||
function splitSearchPath(value) {
|
||||
return value.split(":").map((entry) => entry.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function sourceRelFor(machineRel, path) {
|
||||
return normalizeRel(`configs/sim/${machineRel}/${path}`);
|
||||
}
|
||||
|
||||
function targetPathFor(wasmDir, targetRel) {
|
||||
return `${wasmDir}/${normalizeRel(targetRel)}`;
|
||||
}
|
||||
|
||||
function isUserMCodePath(path) {
|
||||
return /^M1\d\d$/i.test(basename(path));
|
||||
}
|
||||
|
||||
function isSubroutinePath(path) {
|
||||
return path.toLowerCase().endsWith(".ngc");
|
||||
}
|
||||
|
||||
function findUpwardByBasename(manifestSet, sourceDir, fileName, searchRootRel) {
|
||||
let current = sourceDir;
|
||||
while (!searchRootRel || current === searchRootRel || current.startsWith(`${searchRootRel}/`)) {
|
||||
const candidate = normalizeRel(`${current}/${fileName}`);
|
||||
if (manifestSet.has(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
if (!current || current === searchRootRel) {
|
||||
break;
|
||||
}
|
||||
current = dirname(current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function sourceForReference(manifestSet, sourceDir, reference, searchRootRel) {
|
||||
if (!reference || reference.startsWith("/")) {
|
||||
return null;
|
||||
}
|
||||
const sourceRel = normalizeRel(`${sourceDir}/${reference}`);
|
||||
if (manifestSet.has(sourceRel)) {
|
||||
return sourceRel;
|
||||
}
|
||||
return findUpwardByBasename(manifestSet, sourceDir, basename(reference), searchRootRel);
|
||||
}
|
||||
|
||||
function addPlannedFile(plan, manifestSet, sourceRel, targetRel, options = {}) {
|
||||
if (!sourceRel || !manifestSet.has(sourceRel)) {
|
||||
if (options.required) {
|
||||
throw new Error(`missing vendored sim-config file: ${sourceRel}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = plan.get(sourceRel);
|
||||
const executable = options.executable || isUserMCodePath(sourceRel);
|
||||
if (existing) {
|
||||
existing.executable ||= executable;
|
||||
return;
|
||||
}
|
||||
|
||||
plan.set(sourceRel, {
|
||||
sourceRel,
|
||||
wasmPath: targetPathFor(options.wasmDir, targetRel),
|
||||
path: targetPathFor(options.wasmDir, targetRel),
|
||||
executable,
|
||||
});
|
||||
}
|
||||
|
||||
function addDirectoryFiles(plan, manifestEntries, manifestSet, sourceDir, targetDir, predicate, options) {
|
||||
const prefix = sourceDir ? `${sourceDir}/` : "";
|
||||
for (const sourceRel of manifestEntries) {
|
||||
if (!sourceRel.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
const childRel = sourceRel.slice(prefix.length);
|
||||
if (childRel.includes("/") || !predicate(sourceRel)) {
|
||||
continue;
|
||||
}
|
||||
addPlannedFile(plan, manifestSet, sourceRel, normalizeRel(`${targetDir}/${childRel}`), options);
|
||||
}
|
||||
}
|
||||
|
||||
function remapNgcNames(iniValues) {
|
||||
const names = [];
|
||||
for (const remap of allIniValues(iniValues, "RS274NGC", "REMAP")) {
|
||||
const match = remap.match(/(?:^|\s)ngc=([^\s]+)/i);
|
||||
if (match) {
|
||||
names.push(`${match[1]}.ngc`);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
function sectionValues(iniValues, section) {
|
||||
const prefix = `${section.toUpperCase()}.`;
|
||||
const values = [];
|
||||
for (const [key, entries] of iniValues.entries()) {
|
||||
if (!key.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
for (const value of entries) {
|
||||
values.push({ key: key.slice(prefix.length), value });
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function valuesMatching(iniValues, section, keys) {
|
||||
const wanted = new Set(keys.map((key) => key.toUpperCase()));
|
||||
return sectionValues(iniValues, section)
|
||||
.filter((entry) => wanted.has(entry.key))
|
||||
.map((entry) => entry.value);
|
||||
}
|
||||
|
||||
function looksLikePythonReference(value) {
|
||||
return /(^|\s|=|:)["']?[^"'\s]*\.py(["'\s]|$)/i.test(value);
|
||||
}
|
||||
|
||||
function userMCodesInText(text) {
|
||||
const codes = new Set();
|
||||
for (const match of text.matchAll(/(?<![A-Za-z0-9_])M\s*(1\d\d)(?![0-9])/gi)) {
|
||||
codes.add(`M${match[1]}`);
|
||||
}
|
||||
return [...codes].sort();
|
||||
}
|
||||
|
||||
function vendoredUserMCodes({
|
||||
manifestEntries,
|
||||
sourceDir,
|
||||
normalizedSearchRoot,
|
||||
userMPathValues,
|
||||
}) {
|
||||
const manifestSet = new Set(manifestEntries);
|
||||
const executableCodes = new Set();
|
||||
for (const dirEntry of userMPathValues.flatMap(splitSearchPath)) {
|
||||
if (dirEntry.startsWith("/")) {
|
||||
continue;
|
||||
}
|
||||
const sourceUserMDir = normalizeRel(`${sourceDir}/${dirEntry}`);
|
||||
const prefix = sourceUserMDir ? `${sourceUserMDir}/` : "";
|
||||
for (const sourceRel of manifestEntries) {
|
||||
if (
|
||||
sourceRel.startsWith(prefix) &&
|
||||
!sourceRel.slice(prefix.length).includes("/") &&
|
||||
isUserMCodePath(sourceRel)
|
||||
) {
|
||||
executableCodes.add(basename(sourceRel).toUpperCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const dirEntry of userMPathValues.flatMap(splitSearchPath)) {
|
||||
if (dirEntry.startsWith("/")) {
|
||||
continue;
|
||||
}
|
||||
const sourceUserMDir = normalizeRel(`${sourceDir}/${dirEntry}`);
|
||||
for (let code = 100; code <= 199; code += 1) {
|
||||
const candidate = findUpwardByBasename(
|
||||
manifestSet,
|
||||
sourceUserMDir,
|
||||
`M${code}`,
|
||||
normalizedSearchRoot,
|
||||
);
|
||||
if (candidate) {
|
||||
executableCodes.add(`M${code}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...executableCodes].sort();
|
||||
}
|
||||
|
||||
export function analyzeIniRuntimeBoundaries({
|
||||
manifestText = "",
|
||||
sourceRootRel,
|
||||
sourceSearchRootRel = sourceRootRel,
|
||||
iniFile = "",
|
||||
iniText,
|
||||
executionTexts = [],
|
||||
}) {
|
||||
const manifestEntries = cleanManifest(manifestText);
|
||||
const iniValues = parseIni(iniText);
|
||||
const normalizedSourceRoot = normalizeRel(sourceRootRel);
|
||||
const normalizedSearchRoot = normalizeRel(sourceSearchRootRel);
|
||||
const sourceDir = normalizeRel(`${normalizedSourceRoot}/${dirname(iniFile)}`);
|
||||
const userMPathValues = allIniValues(iniValues, "RS274NGC", "USER_M_PATH");
|
||||
const halValues = valuesMatching(iniValues, "HAL", [
|
||||
"HALFILE",
|
||||
"HALCMD",
|
||||
"POSTGUI_HALFILE",
|
||||
"HALUI",
|
||||
]);
|
||||
const displayValues = valuesMatching(iniValues, "DISPLAY", [
|
||||
"DISPLAY",
|
||||
"PYVCP",
|
||||
"GLADEVCP",
|
||||
"EMBED_TAB_COMMAND",
|
||||
]);
|
||||
const halUiMdiCommands = allIniValues(iniValues, "HALUI", "MDI_COMMAND");
|
||||
const dbProgram = firstIniValue(iniValues, "EMCIO", "DB_PROGRAM");
|
||||
const remapValues = allIniValues(iniValues, "RS274NGC", "REMAP");
|
||||
const pythonRemapReferences = [
|
||||
...sectionValues(iniValues, "PYTHON").map((entry) => entry.value),
|
||||
...remapValues.filter((value) => /(?:^|\s)python=/i.test(value)),
|
||||
];
|
||||
const pythonUiReferences = [
|
||||
...displayValues.filter(looksLikePythonReference),
|
||||
...(dbProgram && looksLikePythonReference(dbProgram) ? [dbProgram] : []),
|
||||
];
|
||||
const pythonReferences = [...pythonRemapReferences, ...pythonUiReferences];
|
||||
const vendoredUserMCodeList = vendoredUserMCodes({
|
||||
manifestEntries,
|
||||
sourceDir,
|
||||
normalizedSearchRoot,
|
||||
userMPathValues,
|
||||
});
|
||||
const vendoredUserMCodeSet = new Set(vendoredUserMCodeList);
|
||||
const hasUserMPath = userMPathValues.length > 0;
|
||||
const executionUserMCodes = [...new Set(executionTexts.flatMap(userMCodesInText))].sort();
|
||||
const unstagedExecutionUserMCodes = executionUserMCodes
|
||||
.filter((code) => !vendoredUserMCodeSet.has(code));
|
||||
const hasExternalUserMUse = hasUserMPath && unstagedExecutionUserMCodes.length > 0;
|
||||
|
||||
const dependencies = [];
|
||||
if (dbProgram) {
|
||||
dependencies.push("tool_database_process");
|
||||
}
|
||||
if (halValues.length > 0) {
|
||||
dependencies.push("hal_process");
|
||||
}
|
||||
if (displayValues.some((value) => !/^axis$/i.test(value))) {
|
||||
dependencies.push("ui_process");
|
||||
}
|
||||
if (halUiMdiCommands.length > 0) {
|
||||
dependencies.push("halui_mdi_process");
|
||||
}
|
||||
if (pythonReferences.length > 0) {
|
||||
dependencies.push("python_runtime");
|
||||
}
|
||||
if (hasExternalUserMUse) {
|
||||
dependencies.push("external_user_m_process");
|
||||
}
|
||||
|
||||
let recommendedBlockedKind = "-";
|
||||
if (dbProgram && dbProgram !== "./db_nonran.py") {
|
||||
recommendedBlockedKind = "L4-TOOL-DB";
|
||||
} else if (hasExternalUserMUse) {
|
||||
recommendedBlockedKind = "L4-USER-M-PROCESS";
|
||||
} else if (pythonRemapReferences.length > 0) {
|
||||
recommendedBlockedKind = "L4-PYTHON-REMAP";
|
||||
}
|
||||
|
||||
return {
|
||||
dependencies: [...new Set(dependencies)].sort(),
|
||||
recommendedBlockedKind,
|
||||
toolDatabaseProgram: dbProgram ?? "",
|
||||
halRuntime: {
|
||||
values: halValues,
|
||||
requiresProcess: halValues.length > 0,
|
||||
},
|
||||
uiRuntime: {
|
||||
values: displayValues,
|
||||
requiresProcess: displayValues.some((value) => !/^axis$/i.test(value)),
|
||||
},
|
||||
haluiRuntime: {
|
||||
mdiCommands: halUiMdiCommands,
|
||||
requiresProcess: halUiMdiCommands.length > 0,
|
||||
},
|
||||
userMRuntime: {
|
||||
paths: userMPathValues,
|
||||
vendoredExecutableCount: vendoredUserMCodeList.length,
|
||||
executionCodes: executionUserMCodes,
|
||||
unstagedExecutionCodes: unstagedExecutionUserMCodes,
|
||||
requiresExternalProcess: hasExternalUserMUse,
|
||||
},
|
||||
pythonRuntime: {
|
||||
references: pythonReferences,
|
||||
remapReferences: pythonRemapReferences,
|
||||
uiReferences: pythonUiReferences,
|
||||
requiresProcess: pythonReferences.length > 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function planSimConfigStaging({
|
||||
manifestText,
|
||||
machineRel,
|
||||
iniFile,
|
||||
iniText,
|
||||
programFile = null,
|
||||
wasmDir = `/work/sim/${machineRel}`,
|
||||
}) {
|
||||
return planIniFileContextStaging({
|
||||
manifestText,
|
||||
sourceRootRel: `configs/sim/${machineRel}`,
|
||||
sourceSearchRootRel: "configs/sim",
|
||||
iniFile,
|
||||
iniText,
|
||||
programFile,
|
||||
wasmDir,
|
||||
});
|
||||
}
|
||||
|
||||
export function planIniFileContextStaging({
|
||||
manifestText,
|
||||
sourceRootRel,
|
||||
iniFile,
|
||||
iniText,
|
||||
programFile = null,
|
||||
wasmDir,
|
||||
sourceSearchRootRel = sourceRootRel,
|
||||
}) {
|
||||
const manifestEntries = cleanManifest(manifestText);
|
||||
const manifestSet = new Set(manifestEntries);
|
||||
const iniValues = parseIni(iniText);
|
||||
const plan = new Map();
|
||||
const normalizedSourceRoot = normalizeRel(sourceRootRel);
|
||||
const normalizedSearchRoot = normalizeRel(sourceSearchRootRel);
|
||||
const sourceDir = normalizeRel(`${normalizedSourceRoot}/${dirname(iniFile)}`);
|
||||
const iniSourceRel = normalizeRel(`${normalizedSourceRoot}/${iniFile}`);
|
||||
const programReference = programFile ?? firstIniValue(iniValues, "DISPLAY", "OPEN_FILE");
|
||||
|
||||
addPlannedFile(plan, manifestSet, iniSourceRel, iniFile, { wasmDir, required: true });
|
||||
if (programReference) {
|
||||
const programSourceRel = sourceForReference(
|
||||
manifestSet,
|
||||
sourceDir,
|
||||
programReference,
|
||||
normalizedSearchRoot,
|
||||
);
|
||||
addPlannedFile(plan, manifestSet, programSourceRel, programReference, {
|
||||
wasmDir,
|
||||
required: true,
|
||||
});
|
||||
}
|
||||
|
||||
for (const [section, key] of [
|
||||
["EMCIO", "TOOL_TABLE"],
|
||||
["RS274NGC", "PARAMETER_FILE"],
|
||||
]) {
|
||||
const reference = firstIniValue(iniValues, section, key);
|
||||
if (!reference) {
|
||||
continue;
|
||||
}
|
||||
const sourceRel = sourceForReference(manifestSet, sourceDir, reference, normalizedSearchRoot);
|
||||
addPlannedFile(plan, manifestSet, sourceRel, reference, { wasmDir });
|
||||
}
|
||||
|
||||
const subroutineDirs = allIniValues(iniValues, "RS274NGC", "SUBROUTINE_PATH")
|
||||
.flatMap(splitSearchPath);
|
||||
for (const dirEntry of subroutineDirs) {
|
||||
if (dirEntry.startsWith("/")) {
|
||||
continue;
|
||||
}
|
||||
const sourceSubdir = normalizeRel(`${sourceDir}/${dirEntry}`);
|
||||
const targetSubdir = normalizeRel(dirEntry);
|
||||
addDirectoryFiles(
|
||||
plan,
|
||||
manifestEntries,
|
||||
manifestSet,
|
||||
sourceSubdir,
|
||||
targetSubdir,
|
||||
isSubroutinePath,
|
||||
{ wasmDir },
|
||||
);
|
||||
}
|
||||
|
||||
for (const dirEntry of allIniValues(iniValues, "RS274NGC", "USER_M_PATH").flatMap(splitSearchPath)) {
|
||||
if (dirEntry.startsWith("/")) {
|
||||
continue;
|
||||
}
|
||||
const sourceUserMDir = normalizeRel(`${sourceDir}/${dirEntry}`);
|
||||
const targetUserMDir = normalizeRel(dirEntry);
|
||||
addDirectoryFiles(
|
||||
plan,
|
||||
manifestEntries,
|
||||
manifestSet,
|
||||
sourceUserMDir,
|
||||
targetUserMDir,
|
||||
isUserMCodePath,
|
||||
{ wasmDir, executable: true },
|
||||
);
|
||||
}
|
||||
|
||||
for (const remapName of remapNgcNames(iniValues)) {
|
||||
for (const dirEntry of subroutineDirs) {
|
||||
const sourceRel = sourceForReference(
|
||||
manifestSet,
|
||||
normalizeRel(`${sourceDir}/${dirEntry}`),
|
||||
remapName,
|
||||
normalizedSearchRoot,
|
||||
);
|
||||
addPlannedFile(plan, manifestSet, sourceRel, normalizeRel(`${dirEntry}/${remapName}`), {
|
||||
wasmDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
wasmDir,
|
||||
iniPath: targetPathFor(wasmDir, iniFile),
|
||||
programPath: programReference ? targetPathFor(wasmDir, programReference) : null,
|
||||
files: [...plan.values()],
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user