Add AC/BC RTCP programs and 3D machine manager

This commit is contained in:
2026-09-20 12:31:12 +08:00
parent 1c1e5f9672
commit 8aab531b02
363 changed files with 53213 additions and 58 deletions

View File

@@ -683,11 +683,19 @@ export class SimSession {
const joints = Array.isArray(current.state.joint_commanded) && current.state.joint_commanded.length === 5
? [...current.state.joint_commanded]
: [...this.xyzacKinematics.snapshot().joints];
const selected = this.xyzacKinematics.select(mode, joints);
// A switchkins button changes the coordinate interpretation of the
// stopped machine. Preserve the physical joint sample and recompute
// only the coordinate pose in the new kinematics. `select()` converts
// the old pose back into the target mode, which changes joints and makes
// an idle M429/M428 button appear to move the machine.
const selected = this.xyzacKinematics.selectPhysical(mode, joints);
if (selected.status !== "verified") return selected;
this.xyzacProgramKinematicsMode = mode;
if (typeof this.wasmMotion.setActiveKinematicsTag === "function") this.wasmMotion.setActiveKinematicsTag(mode);
if (selected.pose && typeof this.wasmMotion.setPosition === "function") this.wasmMotion.setPosition(selected.pose);
if (selected.pose && typeof this.wasmMotion.setPosition === "function") this.wasmMotion.setPosition(selected.pose, mode);
if (selected.pose && this.xyzacProgram) {
const synchronized = this.xyzacProgram.synchronizePosition(selected.pose);
if (synchronized.status !== "verified") return synchronized;
}
return { ...selected, kinematics: this.xyzacKinematics.snapshot(), wasm_motion: this.wasmMotion.read() };
}
@@ -1457,22 +1465,27 @@ export class SimSession {
});
}
if (Number.isSafeInteger(input.simulation_tick) && input.simulation_tick >= this.tick) this.tick = input.simulation_tick;
const pausedStep = input.kind === "SIM_STEP" && this.xyzacProgram && (this.task.state.state === "PAUSED" || this.xyzacProgram.snapshot().status === 3);
const eventTick = input.kind === "SIM_STEP" && Number.isSafeInteger(input.payload?.cycles) && !pausedStep
? this.tick + input.payload.cycles : this.tick;
try {
this.timeline.appendEvent(input.seq, eventTick, input.kind, input.payload ?? {});
} catch (error) {
if (text(input.operation_id)) this.operations.delete(input.operation_id);
return this.error(input, "event_log", String(error?.message ?? error));
}
const coreEventResult = appendCoreEvent(this.core, input.seq, eventTick, input.kind, input.payload ?? {});
if (coreEventResult !== 0) {
if (text(input.operation_id)) this.operations.delete(input.operation_id);
return this.error(input, "event_log", "core_event_append_failed");
// SIM_STEP may stop partway through its requested cycles. Record its
// actual end tick, never a future tick that poisons subsequent commands.
let eventRecorded = false;
const recordEvent = () => {
if (eventRecorded) return;
this.timeline.appendEvent(input.seq, this.tick, input.kind, input.payload ?? {});
eventRecorded = true;
const result = appendCoreEvent(this.core, input.seq, this.tick, input.kind, input.payload ?? {});
if (result !== 0) throw new Error("core_event_append_failed");
};
if (input.kind !== "SIM_STEP") {
try { recordEvent(); }
catch (error) {
if (text(input.operation_id)) this.operations.delete(input.operation_id);
return this.error(input, "event_log", String(error?.message ?? error));
}
}
const finish = (response, status = "verified") => {
try { recordEvent(); }
catch (error) { return this.error(input, "event_log", String(error?.message ?? error)); }
if (text(input.operation_id) && OPERATION_KINDS.has(input.kind)) {
const operation = this.operations.get(input.operation_id);
if (operation) {
@@ -2174,8 +2187,26 @@ export class SimSession {
},
});
} catch (error) {
return finish(this.error(input, "scheduler", String(error?.message ?? error)), "failed");
// The scheduler may have consumed a partial task cycle. Align all
// transport clocks to that attempted tick and leave motion stopped.
this.tick = this.scheduler.tick;
this.task.setTick(this.tick);
call(this.core, "sim_protocol_set_tick", this.tick);
if (this.xyzacProgram) {
this.wasmMotion.abort();
this.pendingXyzacMotion = null;
if (this.xyzacProgram.snapshot().status === 2) this.xyzacProgram.abort();
if (this.task.state.state === "RUNNING") this.task.transition("STOPPED");
}
const reason = String(error?.message ?? error);
const limit = reason === "motion_joint_limit";
return finish(this.envelope("ERROR", input, {
status: limit ? "blocked" : "error", code: limit ? "motion_limit" : "scheduler",
message_key: limit ? "关节到达限位,运动已停止;可反向 Jog" : reason,
reason, motion_stopped: Boolean(this.xyzacProgram), completed_cycles: this.scheduler.last_cycles,
}), "failed");
}
recordEvent();
const program = this.xyzacProgram ? this.xyzacProgram.snapshot() : this.program.loaded ? this.program.snapshot() : null;
const execution_trace = program_steps
.map((step) => ({
@@ -2730,6 +2761,10 @@ export class SimSession {
})();
if (jog.status !== "verified") {
const code = jog.code === "linuxcnc_tp_jog_-1217" ? "motion_limit_range" : jog.code === "linuxcnc_tp_jog_-1215" ? "motion_jog_nonfinite" : jog.code;
if (code === "motion_limit_range") return finish(this.envelope("ERROR", input, {
status: "blocked", code: "motion_limit", message_key: code,
reason: "Jog 超出行程范围;请反向移动或减小增量", command: input.kind,
}), "failed");
return finish(this.error(input, "motion_queue", code), "failed");
}
return finish(this.envelope("COMMAND_RESULT", input, { status: "verified", command: input.kind, motion: this.wasmMotionProjection(), wasm_motion: this.wasmMotion.read(), motion_authority: "wasm", operation_id: input.operation_id ?? null }), "verified");