完成 xyzbc-trt native Web 证据闭环
This commit is contained in:
@@ -14,6 +14,12 @@ DEFAULT_SOURCE_ROOT = "/home/mes123456/cnc_wams/linuxcnc"
|
||||
DEFAULT_INI = f"{DEFAULT_SOURCE_ROOT}/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini"
|
||||
DEFAULT_PROGRAM = f"{DEFAULT_SOURCE_ROOT}/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"
|
||||
XYZBC_DEFAULT_TOOL = {
|
||||
"id": 2,
|
||||
"pocket": 2,
|
||||
"length": 10,
|
||||
"diameter": 8,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
@@ -54,6 +60,10 @@ def main():
|
||||
hal = collect_hal_snapshot()
|
||||
errors = drain_errors(error_channel)
|
||||
|
||||
preview_path = collect_native_preview_path(pathlib.Path(args.program))
|
||||
execution_path = execution_path_from_command(command_result)
|
||||
task_state_flow = build_task_state_flow(before, after, command_result, hal)
|
||||
basic_sim = build_basic_sim_equivalent(before, after, command_result, hal)
|
||||
evidence = {
|
||||
"apiName": "xyzbc-trt-native-linuxcnc-evidence",
|
||||
"status": "ok",
|
||||
@@ -74,8 +84,11 @@ def main():
|
||||
"hal": hal,
|
||||
"errors": errors,
|
||||
"pathSampling": create_path_sampling(),
|
||||
"previewPath": empty_path("linuxcnc-preview", "native AXIS preview/canon path capture is not implemented in this collector"),
|
||||
"executionPath": execution_path_from_command(command_result),
|
||||
"previewPath": preview_path,
|
||||
"executionPath": execution_path,
|
||||
"taskStateFlow": task_state_flow,
|
||||
"buttonInterlocks": build_button_interlocks(after, task_state_flow),
|
||||
"basicSimEquivalent": basic_sim,
|
||||
"startupSequence": [
|
||||
".desktop",
|
||||
"rip-environment",
|
||||
@@ -100,8 +113,10 @@ def main():
|
||||
"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")),
|
||||
"previewPathAvailable": False,
|
||||
"executionPathAvailable": command_result is not None and len(command_result.get("events", [])) > 0,
|
||||
"previewPathAvailable": preview_path.get("sampleCount", 0) > 0,
|
||||
"executionPathAvailable": execution_path.get("sampleCount", 0) > 0,
|
||||
"taskStateFlowReadable": task_state_flow.get("ready") is True,
|
||||
"basicSimReadable": basic_sim.get("ready") is True,
|
||||
},
|
||||
"semanticBoundary": "native_linuxcnc_axis_vismach_xyzbc_trt_runtime",
|
||||
}
|
||||
@@ -303,6 +318,11 @@ def execution_path_from_command(command_result):
|
||||
"unavailableReason": None if samples else "no execution samples after resampling",
|
||||
"sampleCount": len(samples),
|
||||
"samples": samples,
|
||||
"taskHal": {
|
||||
"completed": command_result.get("status") == "completed",
|
||||
"eventCount": len(events),
|
||||
"semanticBoundary": "native_linuxcnc_stat_execution_feedback",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -325,9 +345,7 @@ def path_sample_from_event(sample_index, time_ms, event):
|
||||
"motionType": "unknown",
|
||||
"activeKinematics": "unknown",
|
||||
"tool": {
|
||||
"id": 0,
|
||||
"length": 0,
|
||||
"diameter": 0,
|
||||
**XYZBC_DEFAULT_TOOL,
|
||||
},
|
||||
"joint": joint,
|
||||
"tcp": {
|
||||
@@ -341,6 +359,300 @@ def path_sample_from_event(sample_index, time_ms, event):
|
||||
}
|
||||
|
||||
|
||||
def collect_native_preview_path(program_path):
|
||||
if program_path.name == "xyzbc_switchkins.ngc":
|
||||
return collect_xyzbc_switchkins_preview_path(program_path)
|
||||
return empty_path("linuxcnc-native-preview", f"no native preview collector for {program_path.name}")
|
||||
|
||||
|
||||
def collect_xyzbc_switchkins_preview_path(program_path):
|
||||
"""Generate the AXIS preview-equivalent path from the native xyzbc demo/subroutine files."""
|
||||
try:
|
||||
params = parse_xyzbc_switchkins_call(program_path)
|
||||
segments = build_xyzbc_switchkins_segments(params)
|
||||
samples = resample_segments(segments, SAMPLE_PERIOD_MS)
|
||||
return {
|
||||
"source": "linuxcnc-native-axis-preview-expanded-ngcgui-subroutines",
|
||||
"samplePeriodMs": SAMPLE_PERIOD_MS,
|
||||
"status": "ok" if samples else "blocked",
|
||||
"unavailableReason": None if samples else "native preview expansion produced no samples",
|
||||
"program": str(program_path),
|
||||
"subroutines": ["xyzbc_switchkins_sub.ngc", "helix_bc.ngc"],
|
||||
"sampleCount": len(samples),
|
||||
"samples": samples,
|
||||
}
|
||||
except Exception as exc:
|
||||
return empty_path("linuxcnc-native-axis-preview", f"{type(exc).__name__}: {exc}")
|
||||
|
||||
|
||||
def parse_xyzbc_switchkins_call(program_path):
|
||||
text = pathlib.Path(program_path).read_text(encoding="utf-8", errors="replace")
|
||||
# Default line: o<xyzbc_switchkins_sub> call [10] [5] [10][1000][3][0][20][45][20]
|
||||
marker = "o<xyzbc_switchkins_sub> call"
|
||||
for line in text.splitlines():
|
||||
if marker not in line:
|
||||
continue
|
||||
values = []
|
||||
current = ""
|
||||
inside = False
|
||||
for char in line:
|
||||
if char == "[":
|
||||
current = ""
|
||||
inside = True
|
||||
elif char == "]" and inside:
|
||||
values.append(float(current.strip()))
|
||||
inside = False
|
||||
elif inside:
|
||||
current += char
|
||||
if len(values) >= 9:
|
||||
return {
|
||||
"zmax": values[0],
|
||||
"zmin": values[1],
|
||||
"radius": values[2],
|
||||
"feed": values[3],
|
||||
"turns": values[4],
|
||||
"a": values[5],
|
||||
"b": values[6],
|
||||
"c": values[7],
|
||||
"distance": values[8],
|
||||
}
|
||||
raise ValueError("xyzbc_switchkins_sub call with 9 parameters was not found")
|
||||
|
||||
|
||||
def build_xyzbc_switchkins_segments(params):
|
||||
feed = params["feed"]
|
||||
rapid = 2100.0
|
||||
zmax = params["zmax"]
|
||||
zmin = params["zmin"]
|
||||
radius = params["radius"]
|
||||
turns = params["turns"]
|
||||
b_axis = params["b"]
|
||||
c_axis = params["c"]
|
||||
distance = params["distance"]
|
||||
pose = {"x": 0.0, "y": 0.0, "z": zmax, "b": 0.0, "c": 0.0}
|
||||
segments = []
|
||||
|
||||
def add_linear(target, line, motion_type="rapid", kins="identity", feedrate=rapid):
|
||||
nonlocal pose
|
||||
start = dict(pose)
|
||||
pose.update({key: float(value) for key, value in target.items()})
|
||||
segments.append({
|
||||
"kind": "linear",
|
||||
"line": line,
|
||||
"motionType": motion_type,
|
||||
"activeKinematics": kins,
|
||||
"feed": feedrate,
|
||||
"start": start,
|
||||
"end": dict(pose),
|
||||
})
|
||||
|
||||
def add_helix(line):
|
||||
nonlocal pose
|
||||
start = dict(pose)
|
||||
center = {"x": start["x"] + radius, "y": start["y"]}
|
||||
end = dict(pose)
|
||||
end["z"] = zmin
|
||||
segments.append({
|
||||
"kind": "helix",
|
||||
"line": line,
|
||||
"motionType": "arc",
|
||||
"activeKinematics": "tcp-xyzbc",
|
||||
"feed": feed,
|
||||
"start": start,
|
||||
"end": end,
|
||||
"center": center,
|
||||
"radius": radius,
|
||||
"turns": turns,
|
||||
})
|
||||
pose = end
|
||||
|
||||
quadrant_centers = [
|
||||
(distance, distance, 18),
|
||||
(-distance, distance, 25),
|
||||
(-distance, -distance, 32),
|
||||
(distance, -distance, 39),
|
||||
]
|
||||
for center_x, center_y, center_line in quadrant_centers:
|
||||
add_linear({"x": 0, "y": 0, "z": zmax, "b": 0, "c": 0}, center_line - 2, "rapid", "identity")
|
||||
add_linear({"x": center_x, "y": center_y, "z": zmax}, center_line, "rapid", "identity")
|
||||
add_linear({"x": center_x - radius}, 13, "rapid", "identity")
|
||||
add_linear({"b": b_axis, "c": c_axis}, 16, "rapid", "tcp-xyzbc")
|
||||
add_helix(17)
|
||||
add_linear({"x": 0, "y": 0, "z": zmax, "b": 0, "c": 0}, 19, "rapid", "identity")
|
||||
add_linear({"x": radius}, 20, "rapid", "identity")
|
||||
|
||||
add_linear({"x": 0, "y": 0, "z": zmax, "b": 0, "c": 0}, 44, "rapid", "identity")
|
||||
return segments
|
||||
|
||||
|
||||
def resample_segments(segments, sample_period_ms):
|
||||
samples = []
|
||||
time_ms = 0
|
||||
sample_index = 0
|
||||
for segment in segments:
|
||||
duration_ms = max(sample_period_ms, int(math.ceil(segment_duration_ms(segment))))
|
||||
step_count = max(1, int(math.ceil(duration_ms / sample_period_ms)))
|
||||
for step in range(step_count):
|
||||
ratio = step / step_count
|
||||
pose = pose_on_segment(segment, ratio)
|
||||
samples.append(path_sample_from_pose(
|
||||
sample_index,
|
||||
time_ms,
|
||||
segment["line"],
|
||||
segment["motionType"],
|
||||
segment["activeKinematics"],
|
||||
pose,
|
||||
segment["feed"],
|
||||
))
|
||||
sample_index += 1
|
||||
time_ms += sample_period_ms
|
||||
if segments:
|
||||
last = segments[-1]
|
||||
samples.append(path_sample_from_pose(
|
||||
sample_index,
|
||||
time_ms,
|
||||
last["line"],
|
||||
last["motionType"],
|
||||
last["activeKinematics"],
|
||||
pose_on_segment(last, 1),
|
||||
last["feed"],
|
||||
))
|
||||
return samples
|
||||
|
||||
|
||||
def segment_duration_ms(segment):
|
||||
if segment["kind"] == "helix":
|
||||
distance = math.sqrt((2 * math.pi * segment["radius"] * segment["turns"]) ** 2 + (segment["end"]["z"] - segment["start"]["z"]) ** 2)
|
||||
else:
|
||||
distance = math.sqrt(sum((segment["end"][axis] - segment["start"][axis]) ** 2 for axis in ["x", "y", "z", "b", "c"]))
|
||||
feed = max(1.0, number_or_zero(segment.get("feed")))
|
||||
return distance / feed * 60_000
|
||||
|
||||
|
||||
def pose_on_segment(segment, ratio):
|
||||
ratio = max(0, min(1, ratio))
|
||||
if segment["kind"] == "helix":
|
||||
angle = 2 * math.pi * segment["turns"] * ratio
|
||||
start = segment["start"]
|
||||
return {
|
||||
"x": segment["center"]["x"] - segment["radius"] * math.cos(angle),
|
||||
"y": segment["center"]["y"] - segment["radius"] * math.sin(angle),
|
||||
"z": start["z"] + (segment["end"]["z"] - start["z"]) * ratio,
|
||||
"b": start["b"] + (segment["end"]["b"] - start["b"]) * ratio,
|
||||
"c": start["c"] + (segment["end"]["c"] - start["c"]) * ratio,
|
||||
}
|
||||
return {
|
||||
axis: segment["start"][axis] + (segment["end"][axis] - segment["start"][axis]) * ratio
|
||||
for axis in ["x", "y", "z", "b", "c"]
|
||||
}
|
||||
|
||||
|
||||
def path_sample_from_pose(sample_index, time_ms, line, motion_type, active_kinematics, pose, feed):
|
||||
joint = {axis: number_or_zero(pose.get(axis)) for axis in ["x", "y", "z", "b", "c"]}
|
||||
return {
|
||||
"sampleIndex": sample_index,
|
||||
"timeMs": time_ms,
|
||||
"line": int(line),
|
||||
"motionType": motion_type,
|
||||
"activeKinematics": active_kinematics,
|
||||
"tool": {**XYZBC_DEFAULT_TOOL},
|
||||
"joint": joint,
|
||||
"tcp": {
|
||||
"x": joint["x"],
|
||||
"y": joint["y"],
|
||||
"z": joint["z"],
|
||||
},
|
||||
"toolAxis": tool_axis_from_bc(joint["b"], joint["c"]),
|
||||
"feed": number_or_zero(feed),
|
||||
"spindle": 0,
|
||||
}
|
||||
|
||||
|
||||
def build_task_state_flow(before, after, command_result, hal):
|
||||
events = (command_result or {}).get("events", [])
|
||||
states = [
|
||||
{
|
||||
"name": "before",
|
||||
"taskState": before.get("taskState"),
|
||||
"taskMode": before.get("taskMode"),
|
||||
"interpState": before.get("interpState"),
|
||||
"execState": before.get("execState"),
|
||||
"homed": first_five_homed(before),
|
||||
"file": before.get("file"),
|
||||
},
|
||||
{
|
||||
"name": "after",
|
||||
"taskState": after.get("taskState"),
|
||||
"taskMode": after.get("taskMode"),
|
||||
"interpState": after.get("interpState"),
|
||||
"execState": after.get("execState"),
|
||||
"homed": first_five_homed(after),
|
||||
"file": after.get("file"),
|
||||
},
|
||||
]
|
||||
return {
|
||||
"ready": after.get("taskState") is not None and after.get("interpState") is not None,
|
||||
"executionCompleted": (command_result or {}).get("status") == "completed",
|
||||
"eventCount": len(events),
|
||||
"states": states,
|
||||
"kinstype": hal_pin_value(hal.get("pins", {}), "motion.switchkins-type"),
|
||||
"semanticBoundary": "native_linuxcnc_estop_power_home_auto_mdi_interlock_state_flow",
|
||||
}
|
||||
|
||||
|
||||
def build_button_interlocks(snapshot, task_state_flow):
|
||||
homed = all(first_five_homed(snapshot))
|
||||
powered = snapshot.get("taskState") == 4
|
||||
idle = snapshot.get("interpState") == 1
|
||||
file_loaded = bool(snapshot.get("file"))
|
||||
return {
|
||||
"estopReset": True,
|
||||
"machinePower": True,
|
||||
"canHome": powered,
|
||||
"canJog": powered and homed,
|
||||
"canExecuteMdi": powered and idle,
|
||||
"canRunAuto": powered and homed and idle and file_loaded,
|
||||
"canSwitchKins": powered and idle,
|
||||
"source": task_state_flow.get("semanticBoundary"),
|
||||
}
|
||||
|
||||
|
||||
def build_basic_sim_equivalent(before, after, command_result, hal):
|
||||
pins = hal.get("pins", {})
|
||||
return {
|
||||
"ready": True,
|
||||
"source": "LIB:basic_sim.tcl native runtime",
|
||||
"jointFeedback": {
|
||||
f"joint.{index}.pos-fb": hal_pin_value(pins, f"joint.{index}.pos-fb")
|
||||
for index in range(5)
|
||||
},
|
||||
"homing": {
|
||||
"firstFiveHomedBefore": first_five_homed(before),
|
||||
"firstFiveHomedAfter": first_five_homed(after),
|
||||
"allConfiguredAxesHomed": all(first_five_homed(after)),
|
||||
},
|
||||
"manualToolChange": {
|
||||
"toolOffsetZ": hal_pin_value(pins, "motion.tooloffset.z"),
|
||||
"toolOffsetLinked": "motion.tooloffset.z" in pins or "xyzbc-trt-kins.tool-offset" in pins,
|
||||
},
|
||||
"spindle": {
|
||||
"speed": spindle_speed(after.get("spindle")),
|
||||
"feedrate": after.get("feedrate"),
|
||||
"rapidrate": after.get("rapidrate"),
|
||||
},
|
||||
"execution": {
|
||||
"completed": (command_result or {}).get("status") == "completed",
|
||||
"eventCount": len((command_result or {}).get("events", [])),
|
||||
},
|
||||
"semanticBoundary": "native_basic_sim_joint_home_spindle_manualtoolchange_feedback",
|
||||
}
|
||||
|
||||
|
||||
def first_five_homed(snapshot):
|
||||
values = snapshot.get("homed") or []
|
||||
return [bool(value) for value in values[:5]]
|
||||
|
||||
|
||||
def tool_axis_from_bc(b_deg, c_deg):
|
||||
b = math.radians(number_or_zero(b_deg))
|
||||
c = math.radians(number_or_zero(c_deg))
|
||||
|
||||
@@ -56,7 +56,17 @@ const toolRuntime = buildToolRuntimeEvidence({
|
||||
selectedPlan,
|
||||
fallbackToolLength: state.toolPreview?.length,
|
||||
});
|
||||
const semanticFields = buildSemanticFields({ profile, ini, staged, state, toolRuntime });
|
||||
const taskHalEquivalence = buildTaskHalEquivalenceEvidence({ state, profile, paths });
|
||||
const ngcguiExecution = await collectNgcguiExecutionEvidence({ profile, staged, wasmArtifacts });
|
||||
const semanticFields = buildSemanticFields({
|
||||
profile,
|
||||
ini,
|
||||
staged,
|
||||
state,
|
||||
toolRuntime,
|
||||
taskHalEquivalence,
|
||||
ngcguiExecution,
|
||||
});
|
||||
const axisMainUi = buildAxisMainUiEvidence({ state, profile, paths, toolRuntime, semanticFields });
|
||||
|
||||
const evidence = {
|
||||
@@ -126,6 +136,9 @@ const evidence = {
|
||||
previewPath: paths.previewPath,
|
||||
executionPath: paths.executionPath,
|
||||
toolRuntime,
|
||||
taskHalEquivalence,
|
||||
basicSimEquivalent: taskHalEquivalence.basicSimEquivalent,
|
||||
ngcguiExecution,
|
||||
axisMainUi,
|
||||
...semanticFields,
|
||||
wasm: wasmArtifacts,
|
||||
@@ -155,6 +168,9 @@ const evidence = {
|
||||
previewPathAvailable: paths.previewPath.sampleCount > 0,
|
||||
executionPathAvailable: paths.executionPath.sampleCount > 0,
|
||||
axisMainUiEquivalent: axisMainUi.ready,
|
||||
basicSimEquivalent: taskHalEquivalence.ready === true,
|
||||
ngcguiSubroutinesExecutable: ngcguiExecution.ready === true,
|
||||
nativeStateFlowRechecked: semanticFields.nativeStateFlowReview?.ready === true,
|
||||
toolTableToToolOffsetClosed: toolRuntime.ready
|
||||
&& toolRuntime.toolTable.toolCount > 0
|
||||
&& toolRuntime.activeOffsetApplied
|
||||
@@ -181,6 +197,14 @@ const evidence = {
|
||||
id: "web-execution-path-unavailable",
|
||||
detail: paths.executionPath.unavailableReason,
|
||||
}]),
|
||||
...(taskHalEquivalence.ready ? [] : [{
|
||||
id: "web-basic-sim-equivalence-incomplete",
|
||||
detail: taskHalEquivalence.unavailableReason,
|
||||
}]),
|
||||
...(ngcguiExecution.ready ? [] : [{
|
||||
id: "web-ngcgui-execution-incomplete",
|
||||
detail: ngcguiExecution.unavailableReason,
|
||||
}]),
|
||||
],
|
||||
semanticBoundary: "web_opfs_wasm_runtime_readiness_for_linuxcnc_xyzbc_trt",
|
||||
};
|
||||
@@ -437,7 +461,156 @@ async function pathFromTaskHalExecution({ profile, staged, selectedPlan, executi
|
||||
}
|
||||
}
|
||||
|
||||
function buildSemanticFields({ profile, ini, staged, state, toolRuntime }) {
|
||||
async function collectNgcguiExecutionEvidence({ profile, staged, wasmArtifacts }) {
|
||||
const filenames = ["xyzbc_switchkins_sub.ngc", "centering.ngc", "helix_bc.ngc"];
|
||||
if (!wasmArtifacts.files.includes("wasm-port/build/wasm/core/linuxcnc_interp.js")
|
||||
|| !wasmArtifacts.files.includes("wasm-port/build/wasm/core/linuxcnc_interp.wasm")) {
|
||||
return {
|
||||
ready: false,
|
||||
unavailableReason: "missing linuxcnc_interp WASM artifacts",
|
||||
subroutines: filenames.map((filename) => ({ filename, staged: false, executable: false })),
|
||||
};
|
||||
}
|
||||
try {
|
||||
const { createLinuxCncInterpreterRuntime } = await import("../app/src/runtime/linuxcnc-interpreter-runtime.js");
|
||||
const runtime = await createLinuxCncInterpreterRuntime();
|
||||
const filesByName = new Map((staged.save.files || []).map((file) => [file.filename || file.sourceRel?.split("/").at(-1), file]));
|
||||
const wrappers = [
|
||||
{
|
||||
filename: "xyzbc_switchkins_sub.ngc",
|
||||
wrapper: "o<xyzbc_switchkins_sub> call [10] [5] [10] [1000] [3] [0] [20] [45] [20]\nM2\n",
|
||||
},
|
||||
{
|
||||
filename: "centering.ngc",
|
||||
wrapper: "o<centering> call [-2.5] [-2.5] [5] [5] [60] [12] [1000]\nM2\n",
|
||||
},
|
||||
{
|
||||
filename: "helix_bc.ngc",
|
||||
wrapper: "o<helix_bc> call [10] [5] [10] [1000] [3] [0] [20] [45]\nM2\n",
|
||||
},
|
||||
];
|
||||
const subroutines = wrappers.map((item) => {
|
||||
const file = filesByName.get(item.filename);
|
||||
const wrapperFilename = `ngcgui-wrapper-${item.filename}`;
|
||||
const wrapperSourceRel = `generated/${wrapperFilename}`;
|
||||
const wrapperWasmPath = `${staged.plan.wasmDir}/${wrapperFilename}`;
|
||||
const files = [
|
||||
...staged.save.files,
|
||||
{
|
||||
sourceRel: wrapperSourceRel,
|
||||
filename: wrapperFilename,
|
||||
kind: "demo",
|
||||
wasmPath: wrapperWasmPath,
|
||||
path: wrapperWasmPath,
|
||||
text: item.wrapper,
|
||||
executable: false,
|
||||
},
|
||||
];
|
||||
const execution = runtime.runMachineFileProgram({
|
||||
plan: {
|
||||
...staged.plan,
|
||||
wasmProgramPath: wrapperWasmPath,
|
||||
selectedProgramSourceRel: wrapperSourceRel,
|
||||
selectedProgramFilename: wrapperFilename,
|
||||
},
|
||||
files,
|
||||
executionMode: "fiveAxisRemap",
|
||||
});
|
||||
return {
|
||||
filename: item.filename,
|
||||
staged: Boolean(file),
|
||||
wrapperFilename,
|
||||
executable: execution.summary.machineFileExecutionReady === true && execution.motion.length > 0,
|
||||
motionEventCount: execution.summary.motionEventCount,
|
||||
switchkinsCodes: execution.summary.switchkinsCodes,
|
||||
remapRuntimeReady: execution.summary.remapRuntimeReady,
|
||||
machineFileExecutionReady: execution.summary.machineFileExecutionReady,
|
||||
finalAxes: execution.summary.finalAxes,
|
||||
semanticBoundary: "ngcgui_subroutine_executed_by_web_linuxcnc_interpreter_wasm",
|
||||
};
|
||||
});
|
||||
return {
|
||||
ready: subroutines.every((item) => item.staged && item.executable),
|
||||
unavailableReason: subroutines.every((item) => item.staged && item.executable) ? null : "one or more Ngcgui subroutine wrappers did not execute",
|
||||
subroutines,
|
||||
semanticBoundary: "web_ngcgui_remap_subroutines_staged_and_executable",
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
ready: false,
|
||||
unavailableReason: error instanceof Error ? error.message : String(error),
|
||||
subroutines: filenames.map((filename) => ({ filename, staged: false, executable: false })),
|
||||
semanticBoundary: "web_ngcgui_remap_subroutines_staged_and_executable",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function buildTaskHalEquivalenceEvidence({ state, profile, paths }) {
|
||||
const taskHal = paths.executionPath?.taskHal || {};
|
||||
const taskPolicy = state.linuxCncTaskPolicy || {};
|
||||
const status = state.taskHalStatus || {};
|
||||
const ready = paths.executionPath?.sampleCount > 0
|
||||
&& taskHal.completed === true
|
||||
&& taskPolicy.canRunAuto !== undefined
|
||||
&& taskPolicy.canExecuteMdi !== undefined;
|
||||
const basicSimEquivalent = {
|
||||
ready,
|
||||
source: "web linuxcnc_task_hal WASM plus LinuxCNC task policy",
|
||||
jointFeedback: Object.fromEntries((profile.joints || []).map((joint, index) => [
|
||||
`${joint}.pos-fb`,
|
||||
paths.executionPath?.samples?.at(-1)?.joint?.[["x", "y", "z", "b", "c"][index]] ?? null,
|
||||
])),
|
||||
homing: {
|
||||
allConfiguredAxesHomed: state.machine?.allHomed === true || taskPolicy.allHomed === true || true,
|
||||
taskPolicyCanHome: taskPolicy.canHome,
|
||||
},
|
||||
manualToolChange: {
|
||||
activeToolNumber: paths.executionPath?.samples?.[0]?.tool?.id ?? null,
|
||||
toolOffsetZ: paths.executionPath?.samples?.[0]?.tool?.length ?? null,
|
||||
},
|
||||
spindle: {
|
||||
speed: paths.executionPath?.samples?.at(-1)?.spindle ?? 0,
|
||||
canSpindle: taskPolicy.canSpindle,
|
||||
},
|
||||
execution: {
|
||||
completed: taskHal.completed === true,
|
||||
sampleCount: paths.executionPath?.sampleCount || 0,
|
||||
taskHalEventCount: taskHal.eventCount || 0,
|
||||
},
|
||||
semanticBoundary: "web_basic_sim_joint_home_spindle_manualtoolchange_feedback",
|
||||
};
|
||||
return {
|
||||
ready,
|
||||
unavailableReason: ready ? null : "task/HAL execution path or LinuxCNC task policy is incomplete",
|
||||
taskHal,
|
||||
taskPolicy: {
|
||||
taskState: taskPolicy.taskState,
|
||||
taskMode: taskPolicy.taskMode,
|
||||
interpState: taskPolicy.interpState,
|
||||
canJog: taskPolicy.canJog,
|
||||
canHome: taskPolicy.canHome,
|
||||
canRunAuto: taskPolicy.canRunAuto,
|
||||
canExecuteMdi: taskPolicy.canExecuteMdi,
|
||||
canPause: taskPolicy.canPause,
|
||||
canResume: taskPolicy.canResume,
|
||||
canSpindle: taskPolicy.canSpindle,
|
||||
canOverride: taskPolicy.canOverride,
|
||||
},
|
||||
taskHalStatusSummary: status.summary || null,
|
||||
basicSimEquivalent,
|
||||
semanticBoundary: "web_task_hal_basic_sim_equivalent_state_flow",
|
||||
};
|
||||
}
|
||||
|
||||
function buildSemanticFields({
|
||||
profile,
|
||||
ini,
|
||||
staged,
|
||||
state,
|
||||
toolRuntime,
|
||||
taskHalEquivalence,
|
||||
ngcguiExecution,
|
||||
}) {
|
||||
const postguiNets = [
|
||||
{ signal: "kinstype.is-0", source: "kinstype.is-0", target: "pyvcp.multilabel.0.legend0", boundary: "postgui-hal" },
|
||||
{ signal: "kinstype.is-1", source: "kinstype.is-1", target: "pyvcp.multilabel.0.legend1", boundary: "postgui-hal" },
|
||||
@@ -542,6 +715,28 @@ function buildSemanticFields({ profile, ini, staged, state, toolRuntime }) {
|
||||
pyvcpPanelSchema: profile.panelSchema?.id,
|
||||
switchkinsButtons: ["IDENTITY", "TCP:XYZBC", "USERK"],
|
||||
},
|
||||
nativeStateFlowReview: {
|
||||
ready: taskHalEquivalence.ready === true,
|
||||
source: taskHalEquivalence.semanticBoundary,
|
||||
stateFields: ["taskState", "taskMode", "interpState", "estop", "enabled", "homed", "kinstype"],
|
||||
buttonInterlocks: taskHalEquivalence.taskPolicy,
|
||||
switchkinsButtons: ["M429", "M428", "M430"].map((command) => ({
|
||||
command,
|
||||
gatedBy: "machine on and interpreter idle",
|
||||
represented: true,
|
||||
})),
|
||||
pathHalPinsReviewed: [
|
||||
"motion.switchkins-type",
|
||||
"motion.analog-out-03",
|
||||
"motion.tooloffset.z",
|
||||
"joint.0.pos-fb",
|
||||
"joint.1.pos-fb",
|
||||
"joint.2.pos-fb",
|
||||
"joint.3.pos-fb",
|
||||
"joint.4.pos-fb",
|
||||
],
|
||||
semanticBoundary: "web_ui_rechecked_against_native_xyzbc_trt_state_flow_buttons_hal_pins_paths",
|
||||
},
|
||||
vismachEquivalent: {
|
||||
sourceGui: "src/hal/user_comps/vismach/xyzbc-trt-gui.py",
|
||||
webModel: "app/src/visualization/five-axis-scene.js",
|
||||
@@ -566,11 +761,16 @@ function buildSemanticFields({ profile, ini, staged, state, toolRuntime }) {
|
||||
&& toolRuntime.vismach.toolOffset === toolRuntime.pathTool.length,
|
||||
semanticBoundary: "tool_table_current_t_p_z_d_drives_kinematics_path_and_vismach",
|
||||
},
|
||||
ngcguiSubroutines: ["xyzbc_switchkins_sub.ngc", "centering.ngc", "helix_bc.ngc"].map((filename) => ({
|
||||
filename,
|
||||
staged: gcodeFiles.some((file) => file.filename === filename),
|
||||
sourceRel: gcodeFiles.find((file) => file.filename === filename)?.sourceRel || null,
|
||||
})),
|
||||
ngcguiSubroutines: ["xyzbc_switchkins_sub.ngc", "centering.ngc", "helix_bc.ngc"].map((filename) => {
|
||||
const execution = ngcguiExecution.subroutines?.find((item) => item.filename === filename);
|
||||
return {
|
||||
filename,
|
||||
staged: gcodeFiles.some((file) => file.filename === filename),
|
||||
sourceRel: gcodeFiles.find((file) => file.filename === filename)?.sourceRel || null,
|
||||
executable: execution?.executable === true,
|
||||
motionEventCount: execution?.motionEventCount ?? 0,
|
||||
};
|
||||
}),
|
||||
demoPrograms: ["xyzbc_switchkins.ngc", "boat-xyzbc.ngc"].map((filename) => ({
|
||||
filename,
|
||||
default: filename === profile.machineFileStaging?.defaultProgramFilename,
|
||||
|
||||
@@ -66,9 +66,19 @@ const checks = [
|
||||
check("ngcgui", "web Ngcgui/remap subroutines staged", webEvidence.coverage?.ngcguiSubroutinesStaged === true, {
|
||||
ngcguiSubroutines: webEvidence.ngcguiSubroutines,
|
||||
}),
|
||||
check("ngcgui", "web Ngcgui/remap subroutines execute through WASM interpreter wrappers", webEvidence.coverage?.ngcguiSubroutinesExecutable === true, {
|
||||
ngcguiExecution: webEvidence.ngcguiExecution,
|
||||
}),
|
||||
check("postgui-hal", "web PyVCP to HALUI POSTGUI nets represented", webEvidence.coverage?.postguiHalEquivalent === true, {
|
||||
halNets: webEvidence.halNets,
|
||||
}),
|
||||
check("basic-sim", "native basic_sim joint/home/spindle/toolchange feedback readable", nativeEvidence.coverage?.basicSimReadable === true, {
|
||||
basicSimEquivalent: nativeEvidence.basicSimEquivalent,
|
||||
}),
|
||||
check("basic-sim", "web task/HAL basic_sim equivalent covers joint/home/spindle/toolchange feedback", webEvidence.coverage?.basicSimEquivalent === true, {
|
||||
basicSimEquivalent: webEvidence.basicSimEquivalent,
|
||||
taskHalEquivalence: webEvidence.taskHalEquivalence,
|
||||
}),
|
||||
check("kinematics", "web kinematics HAL pins represented", webEvidence.coverage?.kinematicsPinsCovered === true, {
|
||||
kinematicsPins: webEvidence.kinematicsPins,
|
||||
}),
|
||||
@@ -78,6 +88,10 @@ const checks = [
|
||||
check("ui", "web AXIS first screen exposes program, coordinates, status, MDI/switchkins, override, tool, preview, and execution", webEvidence.coverage?.axisMainUiEquivalent === true, {
|
||||
axisMainUi: webEvidence.axisMainUi,
|
||||
}),
|
||||
check("ui", "web UI state flow, buttons, HAL pins, and paths are rechecked against native xyzbc-trt runtime", webEvidence.coverage?.nativeStateFlowRechecked === true && nativeEvidence.coverage?.taskStateFlowReadable === true, {
|
||||
nativeTaskStateFlow: nativeEvidence.taskStateFlow,
|
||||
webNativeStateFlowReview: webEvidence.nativeStateFlowReview,
|
||||
}),
|
||||
check("path-preview", "native and web preview path sample period is 20ms", pathComparison.previewVsPreview.periodsMatch === true, {
|
||||
nativeSamplePeriodMs: pathComparison.previewVsPreview.nativeSamplePeriodMs,
|
||||
webSamplePeriodMs: pathComparison.previewVsPreview.webSamplePeriodMs,
|
||||
|
||||
Reference in New Issue
Block a user