Align AXIS control flow and fix pause and program rendering

Document source UI gates, explicit native live-plot clearing, and remaining parity boundaries. Fix pause/resume transport continuity, repeated stepping, coordinate previews, and spindle override state. Optimize large program snapshots and rendering, and cover the control flows with native Tcl, WASM, and browser checks.

Validation: 22 native tests and source/browser checks pass. Worker/App regression: 113 of 123 pass; 10 pre-existing failures remain documented with no new failures.
This commit is contained in:
2026-09-08 10:46:27 +08:00
parent 845075f4a3
commit bade560372
55 changed files with 2495 additions and 286 deletions

View File

@@ -26,6 +26,7 @@ struct sim_linuxcnc_tp {
uint32_t last_active_id;
int just_completed;
int initialized;
int step_id;
};
static sim_linuxcnc_tp *active_planner;
@@ -211,11 +212,19 @@ int32_t sim_linuxcnc_tp_jog(sim_linuxcnc_tp *planner, int axis, int direction, d
double *value;
double velocity;
struct state_tag_t state_tag = {0};
if (planner == NULL || !planner->initialized || axis < 0 || axis >= 9 || (direction != -1 && direction != 1) || !isfinite(increment) || increment <= 0.0) return -1215;
if (planner == NULL || !planner->initialized || axis < 0 || axis >= 9 || (direction != -1 && direction != 1) || !isfinite(increment) || increment < 0.0) return -1215;
active_planner = planner;
end = planner->tp.goalPos;
value = axis == 0 ? &end.tran.x : axis == 1 ? &end.tran.y : axis == 2 ? &end.tran.z : axis == 3 ? &end.a : axis == 4 ? &end.b : axis == 5 ? &end.c : axis == 6 ? &end.u : axis == 7 ? &end.v : &end.w;
*value += (double)direction * increment;
// command.c::EMCMOT_JOG_CONT targets the directional jog limit until abort.
// Zero is the continuous-mode encoding; positive increments retain their ABI.
if (increment == 0.0) {
double target = direction > 0 ? planner->axis_max[axis] : planner->axis_min[axis];
if ((target - *value) * direction <= 0.0) return -1217;
*value = target;
} else {
*value += (double)direction * increment;
}
if (!isfinite(*value) || *value < planner->axis_min[axis] || *value > planner->axis_max[axis]) return -1217;
velocity = planner->axis_velocity[axis] * planner->jog_scale;
state_tag.fields_float[GM_FIELD_FLOAT_LINE_NUMBER] = (float)tag;
@@ -257,15 +266,36 @@ int32_t sim_linuxcnc_tp_set_dynamics(sim_linuxcnc_tp *planner, double accelerati
int32_t sim_linuxcnc_tp_set_paused(sim_linuxcnc_tp *planner, int paused) {
if (planner == NULL || !planner->initialized || (paused != 0 && paused != 1)) return -1219;
active_planner = planner;
planner->status.stepping = 0;
if (paused) return tpPause(&planner->tp) == TP_ERR_OK ? 0 : -1220;
return tpResume(&planner->tp) == TP_ERR_OK ? 0 : -1221;
}
int32_t sim_linuxcnc_tp_step_line(sim_linuxcnc_tp *planner) {
if (planner == NULL || !planner->initialized) return -1219;
TC_STRUCT *active = tcqItem(&planner->tp.queue, 0);
if (active == NULL) return -1219;
active_planner = planner;
planner->step_id = active->id;
planner->status.stepping = 1;
return tpResume(&planner->tp) == TP_ERR_OK ? 0 : -1221;
}
int32_t sim_linuxcnc_tp_is_stepping(const sim_linuxcnc_tp *planner) {
return planner != NULL && planner->initialized && planner->status.stepping;
}
double sim_linuxcnc_tp_axis_limit(const sim_linuxcnc_tp *planner, int axis, int maximum) {
if (planner == NULL || !planner->initialized || axis < 0 || axis > 8) return NAN;
return maximum ? planner->axis_max[axis] : planner->axis_min[axis];
}
int32_t sim_linuxcnc_tp_abort(sim_linuxcnc_tp *planner) {
if (planner == NULL || !planner->initialized) return -1222;
active_planner = planner;
planner->last_active_id = 0;
planner->just_completed = 0;
planner->status.stepping = 0;
if (tpAbort(&planner->tp) != TP_ERR_OK) return -1223;
// A browser PROGRAM_ABORT has no background emcmot servo thread after
// the command response. Complete the source TP abort transition here so
@@ -316,9 +346,17 @@ int32_t sim_linuxcnc_tp_step(sim_linuxcnc_tp *planner) {
planner->just_completed = 0;
const int prior_id = tpGetExecId(&planner->tp);
const int prior_depth = tcqLen(&planner->tp.queue);
const int was_stepping = planner->status.stepping;
const int result = tpRunCycle(&planner->tp, (long)llround(planner->tp.cycleTime * 1e9));
// motion/control.c stops a step when the execution ID changes. Compare
// the next queued ID too, before the following TC can start moving.
TC_STRUCT *next = tcqItem(&planner->tp.queue, 0);
if (planner->status.stepping && (next == NULL || next->id != planner->step_id)) {
tpPause(&planner->tp);
planner->status.stepping = 0;
}
planner->tick += 1u;
if (prior_id > 0 && prior_depth > 0 && tcqLen(&planner->tp.queue) == 0) {
if (!was_stepping && prior_id > 0 && prior_depth > 0 && tcqLen(&planner->tp.queue) == 0) {
planner->last_active_id = (uint32_t)prior_id;
planner->just_completed = 1;
}