Make machine configuration authoritative and restore native checkpoints exactly
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
import { fnv1a64 } from "./replay.mjs";
|
||||
import { exportNativeCheckpoint, restoreNativeCheckpoint } from "./native-checkpoint.mjs";
|
||||
import { WEB_SETTINGS } from "../workspace/web-settings.mjs";
|
||||
const DEFAULTS = WEB_SETTINGS.runtime.planner;
|
||||
|
||||
const POSE_BYTES = 72;
|
||||
const STATE_BYTES = 112;
|
||||
@@ -15,7 +18,7 @@ function poseArray(value = {}) { return ["x", "y", "z", "a", "b", "c", "u", "v",
|
||||
function toPose(values) { return Object.fromEntries(["x", "y", "z", "a", "b", "c", "u", "v", "w"].map((axis, index) => [axis, Number(values[index] ?? 0)])); }
|
||||
|
||||
export class WasmLinuxcncTp {
|
||||
constructor(core, { cycleTime = 0.001, velocityLimit = 35, accelerationLimit = 300, limits = {} } = {}) {
|
||||
constructor(core, { cycleTime = DEFAULTS.cycle_time, velocityLimit = DEFAULTS.velocity_limit, accelerationLimit = DEFAULTS.acceleration_limit, limits = {}, axisVelocities = {}, axisAccelerations = {}, maxFeedOverride = DEFAULTS.max_feed_override, blend = DEFAULTS.blend } = {}) {
|
||||
this.core = core;
|
||||
this.available = ["sim_linuxcnc_tp_size", "sim_linuxcnc_tp_queue_capacity", "sim_linuxcnc_tp_init", "sim_linuxcnc_tp_read_state", "sim_linuxcnc_tp_step", "sim_linuxcnc_tp_idle", "sim_linuxcnc_tp_add_line", "sim_linuxcnc_tp_set_profile", "sim_linuxcnc_tp_set_feed_override", "sim_linuxcnc_tp_set_velocity_scale", "sim_linuxcnc_tp_set_jog_scale", "sim_linuxcnc_tp_set_axis_limit", "sim_linuxcnc_tp_jog", "sim_linuxcnc_tp_velocity_scale", "sim_linuxcnc_tp_velocity_limit", "sim_linuxcnc_tp_velocity_mm_min", "sim_linuxcnc_tp_jog_scale", "sim_linuxcnc_tp_jog_velocity", "sim_linuxcnc_tp_feed_override", "sim_linuxcnc_tp_set_dynamics", "sim_linuxcnc_tp_set_paused", "sim_linuxcnc_tp_abort", "sim_linuxcnc_tp_set_goal_position", "sim_linuxcnc_tp_queue_depth", "sim_linuxcnc_tp_queue_segment", "sim_linuxcnc_tp_restore_progress"].every((name) => typeof core?.[`_${name}`] === "function" || typeof core?.[name] === "function");
|
||||
this.cycleTime = cycleTime;
|
||||
@@ -37,7 +40,8 @@ export class WasmLinuxcncTp {
|
||||
this.lastState = null;
|
||||
this.queueDepthOverride = null;
|
||||
this.kinematics = null;
|
||||
this.controls = { profile: 0, lookahead: 50, acceleration_limit: accelerationLimit, jerk_limit: 1e9, feed_override: 1, velocity_scale: 1, jog_scale: 1, paused: false };
|
||||
this.controls = { profile: 0, lookahead: blend.optimization_depth, acceleration_limit: accelerationLimit, jerk_limit: DEFAULTS.compatibility_jerk_limit, feed_override: 1, velocity_scale: 1, jog_scale: 1, paused: false };
|
||||
this.maxFeedOverride = maxFeedOverride;
|
||||
if (!this.available || typeof core?._malloc !== "function") return;
|
||||
this.planner = core._malloc(call(core, "sim_linuxcnc_tp_size", "number", [], []));
|
||||
this.posePointer = core._malloc(POSE_BYTES);
|
||||
@@ -55,6 +59,17 @@ export class WasmLinuxcncTp {
|
||||
const index = { x: 0, y: 1, z: 2, a: 3, b: 4, c: 5, u: 6, v: 7, w: 8 }[axis];
|
||||
if (index !== undefined && Array.isArray(limit) && limit.length === 2) this.setAxisLimit(index, Number(limit[0]), Number(limit[1]));
|
||||
}
|
||||
for (const [axis, velocity] of Object.entries(axisVelocities)) {
|
||||
const index = "xyzabcuvw".indexOf(axis);
|
||||
const configured = call(core, "sim_linuxcnc_tp_set_axis_dynamics", "number", ["number", "number", "number", "number"], [this.planner, index, velocity, axisAccelerations[axis]]);
|
||||
if (configured !== 0) throw new Error(`linuxcnc_tp_axis_dynamics_${axis}_${configured}`);
|
||||
}
|
||||
const feedLimit = call(core, "sim_linuxcnc_tp_set_feed_limit", "number", ["number", "number"], [this.planner, maxFeedOverride]);
|
||||
if (feedLimit !== 0) throw new Error(`linuxcnc_tp_feed_limit_${feedLimit}`);
|
||||
const scale = call(core, "sim_linuxcnc_tp_set_scale_minimum", "number", ["number", "number"], [this.planner, WEB_SETTINGS.ui.motion_scale_min]);
|
||||
if (scale !== 0) throw new Error(`linuxcnc_tp_scale_minimum_${scale}`);
|
||||
const blending = call(core, "sim_linuxcnc_tp_set_blend", "number", Array(7).fill("number"), [this.planner, blend.enable, blend.fallback_enable, blend.optimization_depth, blend.gap_cycles, blend.ramp_frequency, blend.tangent_kink_ratio]);
|
||||
if (blending !== 0) throw new Error(`linuxcnc_tp_blend_${blending}`);
|
||||
}
|
||||
|
||||
setPosition(value) {
|
||||
@@ -247,24 +262,19 @@ export class WasmLinuxcncTp {
|
||||
}
|
||||
} finally { this.core._free(pointer); }
|
||||
}
|
||||
const unsigned = { schema: SOURCE_TP_SCHEMA, cycle_time: this.cycleTime, velocity_limit: this.velocityLimit, controls: { ...this.controls }, execution_state: this.executionState, active_tag: this.activeTag, state: current.state, segments };
|
||||
const unsigned = { schema: SOURCE_TP_SCHEMA, cycle_time: this.cycleTime, velocity_limit: this.velocityLimit, controls: { ...this.controls }, execution_state: this.executionState, active_tag: this.activeTag, state: current.state, segments, segment_tags: [...this.segmentTags], last_active_id: this.lastActiveId, queue_depth_override: this.queueDepthOverride, native_state: exportNativeCheckpoint(this.core, "sim_linuxcnc_tp", this.planner) };
|
||||
return { ...unsigned, state_hash: fnv1a64(unsigned) };
|
||||
}
|
||||
restoreState(value) {
|
||||
if (!value || value.schema !== SOURCE_TP_SCHEMA || value.cycle_time !== this.cycleTime || value.velocity_limit !== this.velocityLimit || value.state_hash !== fnv1a64(Object.fromEntries(Object.entries(value).filter(([key]) => key !== "state_hash")))) throw new Error("linuxcnc_tp_restore_schema");
|
||||
this.reset();
|
||||
this.executionState = Number(value.execution_state ?? 3);
|
||||
this.setProfile(value.controls?.profile ?? 0, value.controls?.tolerance ?? 0);
|
||||
this.setFeedOverride(value.controls?.feed_override ?? 1);
|
||||
this.setVelocityScale(value.controls?.velocity_scale ?? 1);
|
||||
this.setJogScale(value.controls?.jog_scale ?? 1);
|
||||
this.setPosition(value.state?.position ?? {});
|
||||
for (const segment of value.segments ?? []) this.enqueue({ ...segment, kinematics_mode: segment.kinematics_mode ?? value.active_tag ?? 0 });
|
||||
if (this.available && value.state) {
|
||||
this.core.HEAPF64.set(poseArray(value.state.position), this.posePointer / 8);
|
||||
const restored = call(this.core, "sim_linuxcnc_tp_restore_progress", "number", ["number", "number", "number", "number"], [this.planner, this.posePointer, Number(value.state.velocity ?? 0), Number(value.state.tick ?? 0)]);
|
||||
if (restored !== 0) throw new Error(`linuxcnc_tp_restore_progress_${restored}`);
|
||||
}
|
||||
if (!value.native_state || !Array.isArray(value.segment_tags)) throw new Error("linuxcnc_tp_native_checkpoint_required");
|
||||
restoreNativeCheckpoint(this.core, "sim_linuxcnc_tp", value.native_state, this.planner);
|
||||
this.executionState = value.execution_state;
|
||||
this.activeTag = value.active_tag;
|
||||
this.controls = { ...value.controls };
|
||||
this.segmentTags = new Map(value.segment_tags);
|
||||
this.lastActiveId = value.last_active_id;
|
||||
this.queueDepthOverride = value.queue_depth_override;
|
||||
return this.read();
|
||||
}
|
||||
destroy() { for (const pointer of [this.planner, this.posePointer, this.centerPointer, this.normalPointer, this.statePointer]) if (pointer && typeof this.core?._free === "function") this.core._free(pointer); this.planner = this.posePointer = this.centerPointer = this.normalPointer = this.statePointer = 0; }
|
||||
|
||||
Reference in New Issue
Block a user