提交当前项目改动

This commit is contained in:
mes123456
2026-07-02 08:01:34 -04:00
parent 83075c4b96
commit c4a721f6cf
274 changed files with 157294 additions and 1394 deletions

View File

@@ -0,0 +1,282 @@
#!/usr/bin/env python3
import argparse
import json
import math
import pathlib
import subprocess
import sys
import time
AXES = ["X", "Y", "Z", "A", "B", "C", "U", "V", "W"]
DEFAULT_INI = "/home/mes123456/linuxcnc-master/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini"
DEFAULT_PROGRAM = "/home/mes123456/linuxcnc-master/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc"
DEFAULT_OUTPUT = "/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/evidence/native-xyzbc-trt-evidence.json"
def main():
parser = argparse.ArgumentParser(description="Collect native LinuxCNC xyzbc-trt evidence as JSON.")
parser.add_argument("--ini", default=DEFAULT_INI)
parser.add_argument("--program", default=DEFAULT_PROGRAM)
parser.add_argument("--output", default=DEFAULT_OUTPUT)
parser.add_argument("--run", action="store_true", help="Optionally execute the program through linuxcnc.command().")
parser.add_argument("--timeout", type=float, default=60.0)
args = parser.parse_args()
try:
import linuxcnc
except Exception as exc:
write_json(args.output, {
"apiName": "xyzbc-trt-native-linuxcnc-evidence",
"status": "blocked",
"blocker": "python_linuxcnc_import_failed",
"error": f"{type(exc).__name__}: {exc}",
"hint": "Run through /home/mes123456/linuxcnc-master/scripts/rip-environment python3",
})
return 2
stat = linuxcnc.stat()
command = linuxcnc.command()
error_channel = linuxcnc.error_channel()
before = poll_stat(stat)
command_result = None
if args.run:
command_result = run_program(linuxcnc, stat, command, pathlib.Path(args.program), args.timeout)
after = poll_stat(stat)
hal = collect_hal_snapshot()
errors = drain_errors(error_channel)
evidence = {
"apiName": "xyzbc-trt-native-linuxcnc-evidence",
"status": "ok",
"collectedAt": iso_now(),
"executionMode": "auto-run" if args.run else "snapshot-only",
"iniPath": args.ini,
"programPath": args.program,
"programExists": pathlib.Path(args.program).exists(),
"programLineCount": count_program_lines(args.program),
"linuxcncRuntime": {
"pythonApi": True,
"processes": list_processes(),
},
"before": before,
"after": after,
"commandResult": command_result,
"hal": hal,
"errors": errors,
"coverage": {
"axisProfile": before.get("axisMask") == 55 or after.get("axisMask") == 55,
"xyzbcProgramOpen": str(after.get("file") or before.get("file") or "").endswith("xyzbc_switchkins.ngc"),
"switchkinsPinReadable": "motion.switchkins-type" in hal.get("pins", {}),
"jointFeedbackReadable": all(f"joint.{i}.pos-fb" in hal.get("pins", {}) for i in range(5)),
"taskStateReadable": after.get("taskState") is not None,
"positionReadable": bool(after.get("position")),
},
"semanticBoundary": "native_linuxcnc_axis_vismach_xyzbc_trt_runtime",
}
write_json(args.output, evidence)
print(f"native_xyzbc_trt_evidence={args.output}")
return 0
def run_program(linuxcnc, stat, command, program_path, timeout):
result = {
"requested": True,
"program": str(program_path),
"events": [],
"status": "unknown",
}
try:
command.state(linuxcnc.STATE_ESTOP_RESET)
command.wait_complete()
command.state(linuxcnc.STATE_ON)
command.wait_complete()
command.mode(linuxcnc.MODE_AUTO)
command.wait_complete()
command.program_open(str(program_path))
command.wait_complete()
command.auto(linuxcnc.AUTO_RUN, 0)
start = time.time()
while time.time() - start < timeout:
snapshot = poll_stat(stat)
result["events"].append({
"elapsedSeconds": round(time.time() - start, 3),
"interpState": snapshot.get("interpState"),
"execState": snapshot.get("execState"),
"currentLine": snapshot.get("currentLine"),
"readLine": snapshot.get("readLine"),
"position": snapshot.get("position"),
"jointActualPosition": snapshot.get("jointActualPosition"),
})
if snapshot.get("interpState") == linuxcnc.INTERP_IDLE and len(result["events"]) > 2:
result["status"] = "completed"
break
time.sleep(0.05)
else:
result["status"] = "timeout"
except Exception as exc:
result["status"] = "error"
result["error"] = f"{type(exc).__name__}: {exc}"
return result
def poll_stat(stat):
stat.poll()
return {
"taskState": value(stat, "task_state"),
"taskMode": value(stat, "task_mode"),
"interpState": value(stat, "interp_state"),
"execState": value(stat, "exec_state"),
"file": value(stat, "file"),
"currentLine": value(stat, "current_line"),
"readLine": value(stat, "read_line"),
"axisMask": value(stat, "axis_mask"),
"homed": tuple_to_list(value(stat, "homed")),
"position": axes_tuple(value(stat, "position")),
"actualPosition": axes_tuple(value(stat, "actual_position")),
"jointActualPosition": joint_tuple(value(stat, "joint_actual_position")),
"jointPosition": joint_tuple(value(stat, "joint_position")),
"dtg": axes_tuple(value(stat, "dtg")),
"velocity": value(stat, "current_vel"),
"feedrate": value(stat, "feedrate"),
"rapidrate": value(stat, "rapidrate"),
"spindle": normalize_json(value(stat, "spindle")),
}
def collect_hal_snapshot():
pins = {}
commands = [
["halcmd", "show", "pin", "motion.switchkins-type"],
["halcmd", "show", "pin", "motion.analog-out-03"],
["halcmd", "show", "pin", "motion.tooloffset.z"],
["halcmd", "show", "pin", "joint.0.pos-fb"],
["halcmd", "show", "pin", "joint.1.pos-fb"],
["halcmd", "show", "pin", "joint.2.pos-fb"],
["halcmd", "show", "pin", "joint.3.pos-fb"],
["halcmd", "show", "pin", "joint.4.pos-fb"],
["halcmd", "show", "pin", "xyzbc-trt-kins.tool-offset"],
["halcmd", "show", "pin", "xyzbc-trt-kins.x-offset"],
["halcmd", "show", "pin", "xyzbc-trt-kins.z-offset"],
]
raw = []
for command in commands:
completed = subprocess.run(command, text=True, capture_output=True)
raw.append({
"command": command,
"returncode": completed.returncode,
"stdout": completed.stdout,
"stderr": completed.stderr,
})
parse_halcmd_pins(completed.stdout, pins)
return {
"pins": pins,
"raw": raw,
}
def parse_halcmd_pins(text, pins):
for line in text.splitlines():
parts = line.split()
if len(parts) < 5:
continue
name = parts[4]
if "." not in name:
continue
pins[name] = {
"owner": parts[0],
"type": parts[1],
"direction": parts[2],
"value": parse_number(parts[3]),
"linked": "==>" in line or "<==" in line,
"raw": line,
}
def drain_errors(error_channel):
errors = []
for _ in range(20):
error = error_channel.poll()
if not error:
break
errors.append(normalize_json(error))
return errors
def list_processes():
completed = subprocess.run(
["ps", "-ef"],
text=True,
capture_output=True,
)
processes = []
for line in completed.stdout.splitlines():
if "xyzbc-trt" in line or "linuxcncsvr" in line or "milltask" in line or "halui -ini" in line:
processes.append(line)
return processes
def count_program_lines(path):
try:
return len(pathlib.Path(path).read_text(encoding="utf-8", errors="replace").splitlines())
except OSError:
return 0
def value(obj, attr):
return normalize_json(getattr(obj, attr, None))
def axes_tuple(values):
values = tuple_to_list(values)
return {axis.lower(): values[index] for index, axis in enumerate(AXES) if index < len(values)}
def joint_tuple(values):
values = tuple_to_list(values)
return {str(index): values[index] for index in range(min(5, len(values)))}
def tuple_to_list(values):
if values is None:
return []
return [normalize_json(value) for value in values]
def normalize_json(value):
if isinstance(value, float):
if math.isnan(value) or math.isinf(value):
return None
return value
if isinstance(value, (str, int, bool)) or value is None:
return value
if isinstance(value, tuple):
return [normalize_json(item) for item in value]
if isinstance(value, list):
return [normalize_json(item) for item in value]
if isinstance(value, dict):
return {str(key): normalize_json(item) for key, item in value.items()}
return str(value)
def parse_number(value):
try:
return float(value)
except ValueError:
return value
def write_json(path, payload):
output = pathlib.Path(path)
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def iso_now():
return time.strftime("%Y-%m-%dT%H:%M:%S%z")
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,167 @@
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMemorySessionStorage } from "../app/src/runtime/five-axis-session.js";
import { parseLinuxCncIni } from "../app/src/runtime/linuxcnc-ini-runtime.js";
import {
selectMachineFileProgram,
stageProfileMachineFiles,
} from "../app/src/runtime/linuxcnc-machine-file-staging.js";
import { createSimulationStore } from "../app/src/state/store.js";
import { getFiveAxisProfile } from "../app/src/profiles/index.js";
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
const projectRoot = resolve(repoRoot, "web-rtcp-5axis-xyzbc-trt-sim-plan");
const outputPath = process.argv[2]
|| resolve(projectRoot, "working/evidence/web-xyzbc-trt-evidence.json");
const sourceRel = "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc";
const profile = getFiveAxisProfile("xyzbc-trt");
const wasmArtifacts = await inspectWasmArtifacts();
const iniText = await readFile(
resolve(repoRoot, "wasm-port/vendor/linuxcnc", profile.iniPath),
"utf8",
);
const ini = parseLinuxCncIni(iniText, {
path: profile.iniPath,
profileId: profile.id,
});
const storage = createMemorySessionStorage();
const staged = await stageProfileMachineFiles(profile, { storage });
const selectedPlan = selectMachineFileProgram(staged.plan, staged.save, sourceRel);
const store = createSimulationStore();
const storeStage = await store.stageMachineFiles({ storage: createMemorySessionStorage() });
store.dispatch({ type: "LOAD_LINUXCNC_GCODE_SOURCE", sourceRel });
const state = store.getState();
const evidence = {
apiName: "xyzbc-trt-web-opfs-wasm-evidence",
status: wasmArtifacts.ready ? "ready-for-wasm-runtime" : "blocked",
collectedAt: new Date().toISOString(),
profile: {
id: profile.id,
machineName: profile.machineName,
iniPath: profile.iniPath,
pyvcpXmlPath: profile.pyvcpXmlPath,
toolTablePath: profile.toolTablePath,
coordinates: profile.coordinates,
kinematics: profile.kinematics,
kinematicsModuleId: profile.kinematicsModuleId,
defaultProgramFilename: profile.machineFileStaging?.defaultProgramFilename,
switchkinsTypes: profile.kinematicsParameters?.switchkinsTypes,
halPins: profile.halPins,
},
ini: {
ready: ini.validation.ready,
machineName: ini.machineName,
coordinates: ini.traj.coordinates,
kinematicsName: ini.kinematics.name,
kinematicsModuleId: ini.kinematicsModuleId,
remaps: ini.rs274ngc.remaps,
haluiMdiCommands: ini.halui.mdiCommands,
toolTable: ini.emcio.toolTable,
parameterFile: ini.rs274ngc.parameterFile,
jointConfig: ini.jointConfig,
},
opfsStaging: {
storageMode: staged.save.storageMode,
opfsRoot: staged.save.opfsRoot,
fileCount: staged.save.fileCount,
summary: staged.save.summary,
files: staged.save.files.map((file) => ({
sourceRel: file.sourceRel,
opfsPath: file.opfsPath,
wasmPath: file.wasmPath,
kind: file.kind,
bytes: file.bytes,
executable: file.executable,
})),
gcodeSources: staged.save.gcodeSources,
selectedProgram: {
sourceRel: selectedPlan.selectedProgramSourceRel,
filename: selectedPlan.selectedProgramFilename,
wasmProgramPath: selectedPlan.wasmProgramPath,
},
},
store: {
machineProfile: state.machineProfile,
sessionName: state.sessionName,
machineProjectRoot: state.machineProject?.projectRoot || storeStage.save.opfsRoot,
activeProgram: state.activeProgram,
programSource: state.programSource,
programLineCount: state.programLines.length,
selectedGcodeSourceRel: state.machineFileStaging.selectedGcodeSourceRel,
},
wasm: wasmArtifacts,
coverage: {
profileDefaultXyzbc: profile.id === "xyzbc-trt",
iniReady: ini.validation.ready,
opfsStaged: staged.save.status === "saved" && staged.save.fileCount > 0,
pyvcpXmlStaged: staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc-trt.xml")),
remapsStaged: ["428remap.ngc", "429remap.ngc", "430remap.ngc"].every((name) => (
staged.save.files.some((file) => file.sourceRel.endsWith(`/remap_subs/${name}`))
)),
toolTableStaged: staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc-trt.tbl")),
parameterFileStaged: staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc.var")),
defaultProgramStaged: staged.save.gcodeSources.some((source) => source.filename === "xyzbc_switchkins.ngc"),
wasmArtifactsReady: wasmArtifacts.ready,
},
blockers: [
...(wasmArtifacts.ready ? [] : [{
id: "missing-wasm-artifacts",
detail: "wasm-port/build/wasm does not contain all required kinematics/core/tp/task-hal artifacts.",
required: wasmArtifacts.required,
missing: wasmArtifacts.missing,
}]),
...(staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc.var")) ? [] : [{
id: "missing-parameter-file-staging",
detail: "xyzbc.var is referenced by INI but absent from wasm-port/vendor manifest in this workspace.",
}]),
],
semanticBoundary: "web_opfs_wasm_runtime_readiness_for_linuxcnc_xyzbc_trt",
};
await mkdir(dirname(outputPath), { recursive: true });
await writeFile(outputPath, JSON.stringify(evidence, null, 2) + "\n", "utf8");
console.log(`web_xyzbc_trt_evidence=${outputPath}`);
async function inspectWasmArtifacts() {
const required = [
"wasm-port/build/wasm/kinematics/linuxcnc_xyzbc_trt_kinematics.js",
"wasm-port/build/wasm/kinematics/linuxcnc_xyzbc_trt_kinematics.wasm",
"wasm-port/build/wasm/core/linuxcnc_interp.js",
"wasm-port/build/wasm/core/linuxcnc_interp.wasm",
"wasm-port/build/wasm/tp/linuxcnc_tp.js",
"wasm-port/build/wasm/tp/linuxcnc_tp.wasm",
"wasm-port/build/wasm/task-hal/linuxcnc_task_hal.js",
"wasm-port/build/wasm/task-hal/linuxcnc_task_hal.wasm",
];
const files = [];
const missing = [];
for (const rel of required) {
const abs = resolve(repoRoot, rel);
try {
await access(abs);
files.push(rel);
} catch {
missing.push(rel);
}
}
return {
required,
files,
missing,
ready: missing.length === 0,
emscriptenAvailable: Boolean(await commandExists("emcc")),
};
}
async function commandExists(command) {
const { spawn } = await import("node:child_process");
return new Promise((resolveCommand) => {
const child = spawn("bash", ["-lc", `command -v ${command}`], { stdio: "ignore" });
child.on("exit", (code) => resolveCommand(code === 0));
});
}

View File

@@ -0,0 +1,98 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
const projectRoot = resolve(repoRoot, "web-rtcp-5axis-xyzbc-trt-sim-plan");
const nativePath = process.argv[2] || resolve(projectRoot, "working/evidence/native-xyzbc-trt-evidence.json");
const webPath = process.argv[3] || resolve(projectRoot, "working/evidence/web-xyzbc-trt-evidence.json");
const outputPath = process.argv[4] || resolve(projectRoot, "working/evidence/compare-xyzbc-trt-evidence.json");
const nativeEvidence = JSON.parse(await readFile(nativePath, "utf8"));
const webEvidence = JSON.parse(await readFile(webPath, "utf8"));
const checks = [
check("profile", "native axis mask is XYZBC", nativeEvidence.coverage?.axisProfile === true, {
nativeAxisMask: nativeEvidence.after?.axisMask ?? nativeEvidence.before?.axisMask,
}),
check("profile", "web default profile is xyzbc-trt", webEvidence.coverage?.profileDefaultXyzbc === true, {
webProfile: webEvidence.profile?.id,
}),
check("ini", "native loaded xyzbc switchkins program", nativeEvidence.coverage?.xyzbcProgramOpen === true, {
nativeFile: nativeEvidence.after?.file ?? nativeEvidence.before?.file,
}),
check("ini", "web INI parse is ready", webEvidence.coverage?.iniReady === true, {
webIniReady: webEvidence.ini?.ready,
}),
check("switchkins", "native switchkins pin readable", nativeEvidence.coverage?.switchkinsPinReadable === true, {
nativePin: nativeEvidence.hal?.pins?.["motion.switchkins-type"],
}),
check("switchkins", "web remap files staged", webEvidence.coverage?.remapsStaged === true, {
remaps: webEvidence.ini?.remaps,
}),
check("program", "web default xyzbc_switchkins program staged", webEvidence.coverage?.defaultProgramStaged === true, {
selected: webEvidence.opfsStaging?.selectedProgram,
}),
check("tool-table", "web tool table staged", webEvidence.coverage?.toolTableStaged === true, {
toolTable: webEvidence.ini?.toolTable,
}),
check("pyvcp", "web PyVCP XML staged", webEvidence.coverage?.pyvcpXmlStaged === true, {
pyvcpXmlPath: webEvidence.profile?.pyvcpXmlPath,
}),
check("positions", "native joint feedback readable", nativeEvidence.coverage?.jointFeedbackReadable === true, {
joints: nativeEvidence.after?.jointActualPosition,
}),
check("wasm", "required WASM artifacts available", webEvidence.coverage?.wasmArtifactsReady === true, {
missing: webEvidence.wasm?.missing,
}),
check("parameters", "parameter file staged", webEvidence.coverage?.parameterFileStaged === true, {
parameterFile: webEvidence.ini?.parameterFile,
}),
];
const failed = checks.filter((item) => item.status !== "pass");
const blockers = [
...(nativeEvidence.status === "blocked" ? nativeEvidence.blocker ? [nativeEvidence.blocker] : ["native-blocked"] : []),
...(webEvidence.blockers || []).map((blocker) => blocker.id),
];
const report = {
apiName: "xyzbc-trt-native-web-evidence-comparison",
status: failed.length === 0 ? "pass" : "fail",
comparedAt: new Date().toISOString(),
nativePath,
webPath,
summary: {
checkCount: checks.length,
passCount: checks.length - failed.length,
failCount: failed.length,
blockers,
nativeStatus: nativeEvidence.status,
webStatus: webEvidence.status,
},
checks,
requiredImprovements: failed.map((item) => ({
category: item.category,
requirement: item.requirement,
evidence: item.evidence,
})),
semanticBoundary: "native_linuxcnc_vs_web_opfs_wasm_xyzbc_trt_evidence_comparison",
};
await mkdir(dirname(outputPath), { recursive: true });
await writeFile(outputPath, JSON.stringify(report, null, 2) + "\n", "utf8");
console.log(`compare_xyzbc_trt_evidence=${outputPath}`);
if (failed.length > 0) {
console.log(`compare_xyzbc_trt_status=fail fail_count=${failed.length}`);
} else {
console.log("compare_xyzbc_trt_status=pass");
}
function check(category, requirement, passed, evidence = {}) {
return {
category,
requirement,
status: passed ? "pass" : "fail",
evidence,
};
}