Display live WASM machining parameters
This commit is contained in:
@@ -14,6 +14,24 @@
|
|||||||
"task_gate": "sim_task_command_gate",
|
"task_gate": "sim_task_command_gate",
|
||||||
"frontend_role": "input_and_projection_only"
|
"frontend_role": "input_and_projection_only"
|
||||||
},
|
},
|
||||||
|
"live_parameters": {
|
||||||
|
"source": "linuxcnc-wasm",
|
||||||
|
"fields": [
|
||||||
|
"spindle_speed_rpm",
|
||||||
|
"spindle_command_rpm",
|
||||||
|
"program.speed",
|
||||||
|
"velocity_mm_min",
|
||||||
|
"program.feed",
|
||||||
|
"velocity",
|
||||||
|
"velocity_limit",
|
||||||
|
"feed_override",
|
||||||
|
"spindle_override",
|
||||||
|
"queue_depth",
|
||||||
|
"active_id",
|
||||||
|
"program.execution_line"
|
||||||
|
],
|
||||||
|
"status": "verified"
|
||||||
|
},
|
||||||
"switchkins_direct_matrix": {
|
"switchkins_direct_matrix": {
|
||||||
"command": "node --experimental-strip-types web/worker/test_xyzac_trt_case.mjs",
|
"command": "node --experimental-strip-types web/worker/test_xyzac_trt_case.mjs",
|
||||||
"sequence": [0, 1, 2, 0, 1, 2, 0],
|
"sequence": [0, 1, 2, 0, 1, 2, 0],
|
||||||
@@ -57,7 +75,7 @@
|
|||||||
},
|
},
|
||||||
"published_browser_smoke": {
|
"published_browser_smoke": {
|
||||||
"url": "https://49.232.250.35:58010/",
|
"url": "https://49.232.250.35:58010/",
|
||||||
"release": "20260902-linuxcnc-wasm-pause-resume-r11",
|
"release": "20260902-linuxcnc-wasm-live-parameters-r14",
|
||||||
"status": "verified",
|
"status": "verified",
|
||||||
"startup_pause_resume_stop": "verified",
|
"startup_pause_resume_stop": "verified",
|
||||||
"pause_tick": 140,
|
"pause_tick": 140,
|
||||||
@@ -65,6 +83,19 @@
|
|||||||
"stop_state": "IDLE",
|
"stop_state": "IDLE",
|
||||||
"browser_errors": 0,
|
"browser_errors": 0,
|
||||||
"output_sha256": "bdf705473e6cda94d721aa23329257adf4a68b705add7650da5cc47012ab70b0",
|
"output_sha256": "bdf705473e6cda94d721aa23329257adf4a68b705add7650da5cc47012ab70b0",
|
||||||
|
"live_parameters_runtime": {
|
||||||
|
"spindle_actual_rpm": 210,
|
||||||
|
"spindle_command_rpm": 1600,
|
||||||
|
"program_speed_rpm": 1600,
|
||||||
|
"feed_actual_mm_min": 1200,
|
||||||
|
"program_feed_mm_min": 2250000,
|
||||||
|
"tp_velocity_mm_s": 20,
|
||||||
|
"queue_depth": 22,
|
||||||
|
"active_segment": 21,
|
||||||
|
"current_line": 38,
|
||||||
|
"browser_errors": 0,
|
||||||
|
"screenshot_sha256": "7f5307c913d8bc4e60c2a31c29aacc9afd1b0d91fac4a4f22e8a64fc2ea37a1e"
|
||||||
|
},
|
||||||
"continuous_runtime": {
|
"continuous_runtime": {
|
||||||
"sampled_running_frames": 33,
|
"sampled_running_frames": 33,
|
||||||
"queue_gaps": 0,
|
"queue_gaps": 0,
|
||||||
@@ -89,9 +120,9 @@
|
|||||||
"switchkins": "920b85ad7fd6451dfb134255ffdcc4f03a7aa615ebf486c63cde8582e17af195"
|
"switchkins": "920b85ad7fd6451dfb134255ffdcc4f03a7aa615ebf486c63cde8582e17af195"
|
||||||
},
|
},
|
||||||
"resource_sha256": {
|
"resource_sha256": {
|
||||||
"index-EXbw4yiL.js": "6759738df285bc5e2f5c3fb84043dc09fb82292bbc1c3c1011a558c8654c96ab",
|
"index-CDC5yZJC.js": "c3a6aa009e091d5fcb2098c7254313d987229abc1e4765052edb15b465ca0fc4",
|
||||||
"sim-worker-entry-DjXIMZ0D.js": "190515af943d70e93037e88a5cf63b3b8e9cda675a8338cfc1fb4bb954bfd685",
|
"sim-worker-entry-BQKHHWpv.js": "e72d80d9ae0fd679d92d3efbad4dba1eef55f9ce7b54bcd06e4a0d644c3cfb1a",
|
||||||
"linuxcnc-sim-core-D3Qw0e1g.wasm": "f1b1961279cf8ff7ef2e3fcd0068b3ba79de57abe1e56d5c32b16d17568b9536"
|
"linuxcnc-sim-core-fuIvKetC.wasm": "ad4e17ecbf73afbad0d9f069ab20ba3c66b6cc4d75ecdb2b3182673490ee916d"
|
||||||
},
|
},
|
||||||
"authority_dom": {
|
"authority_dom": {
|
||||||
"data-axis-authority": "linuxcnc-wasm-worker",
|
"data-axis-authority": "linuxcnc-wasm-worker",
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ AXIS 源控件清单:菜单 80、工具栏 20、快捷键 10,共 110 条,
|
|||||||
| Feed/Jog/Max/Spindle override | WASM TP/IO getter/setter | 受控 slider,值由快照回读 |
|
| Feed/Jog/Max/Spindle override | WASM TP/IO getter/setter | 受控 slider,值由快照回读 |
|
||||||
| 视图缩放、旋转、平移、tab | React/Three 本地 UI | 仅显示交互,不参与运动计算 |
|
| 视图缩放、旋转、平移、tab | React/Three 本地 UI | 仅显示交互,不参与运动计算 |
|
||||||
|
|
||||||
|
实时参数区显示:主轴实际转速 `speed_rpm`、主轴指令转速 `command_rpm`、G-code `S` 值、TP 实际进给 `velocity_mm_min`、G-code `F` 值、TP 速度/上限、F/S override、队列深度、活动段和当前执行行。所有值来自 WASM program/TP/IO 快照,参数区的 `data-xyzac-parameter-source` 为 `linuxcnc-wasm`;未取得快照时显示 `-`。
|
||||||
|
|
||||||
在 WASM 快照尚未到达前,界面显示 `WASM PENDING` 并禁用运动操作;不会使用 JS 默认轴值、任务状态或 override 值。浏览器定时器只发送 `SIM_STEP` 采样请求,不执行 G-code、轨迹、运动学或设备状态计算。React/Three 中的定时器、相机、网格、刀具网格和文本格式化均为显示层,不产生或修改任何 CNC 状态。
|
在 WASM 快照尚未到达前,界面显示 `WASM PENDING` 并禁用运动操作;不会使用 JS 默认轴值、任务状态或 override 值。浏览器定时器只发送 `SIM_STEP` 采样请求,不执行 G-code、轨迹、运动学或设备状态计算。React/Three 中的定时器、相机、网格、刀具网格和文本格式化均为显示层,不产生或修改任何 CNC 状态。
|
||||||
|
|
||||||
## 按钮/控件矩阵
|
## 按钮/控件矩阵
|
||||||
@@ -192,6 +194,37 @@ screenshot: /tmp/published-pause-resume-final-r11c.png
|
|||||||
screenshot_sha256: 9d1660fd043dd0628ff2989a3e139a3239acd6386d141657a5834c9d2f1bc5a7
|
screenshot_sha256: 9d1660fd043dd0628ff2989a3e139a3239acd6386d141657a5834c9d2f1bc5a7
|
||||||
```
|
```
|
||||||
|
|
||||||
|
发布站点 r13 实时参数验证:
|
||||||
|
|
||||||
|
```text
|
||||||
|
url: https://49.232.250.35:58010/
|
||||||
|
release: 20260902-linuxcnc-wasm-live-parameters-r13
|
||||||
|
parameter_source: linuxcnc-wasm
|
||||||
|
idle: Spindle 0.0 rpm / Feed actual 0.0 mm/min / TP velocity 0.000 mm/s / Planner 0 queued
|
||||||
|
running: Feed actual 540.0 mm/min / TP velocity 9.000 mm/s / Planner 1 queued / Segment 13 / Line 65
|
||||||
|
fields: spindle actual+command+program S, feed actual+program F, TP velocity+limit,
|
||||||
|
F/S override, queue depth, active segment, execution line
|
||||||
|
browser_errors: 0
|
||||||
|
screenshot: /tmp/published-live-parameters-r13.png
|
||||||
|
screenshot_sha256: 4e25750136d9b5a8ed633a15091128a94fc9bbec9af857c71d9bf5dd01270fb6
|
||||||
|
```
|
||||||
|
|
||||||
|
发布站点 r14(程序主轴同步 + 实时参数)验证:
|
||||||
|
|
||||||
|
```text
|
||||||
|
url: https://49.232.250.35:58010/
|
||||||
|
release: 20260902-linuxcnc-wasm-live-parameters-r14
|
||||||
|
resource_sha256: index-CDC5yZJC.js c3a6aa009e091d5fcb2098c7254313d987229abc1e4765052edb15b465ca0fc4
|
||||||
|
sim-worker-entry-BQKHHWpv.js e72d80d9ae0fd679d92d3efbad4dba1eef55f9ce7b54bcd06e4a0d644c3cfb1a
|
||||||
|
linuxcnc-sim-core-fuIvKetC.wasm ad4e17ecbf73afbad0d9f069ab20ba3c66b6cc4d75ecdb2b3182673490ee916d
|
||||||
|
3D_Chips.ngc running: spindle_actual=210 rpm, spindle_command=1600 rpm, program_S=1600 rpm,
|
||||||
|
feed_actual=1200 mm/min, program_F=2250000 mm/min,
|
||||||
|
tp_velocity=20 mm/s, queue_depth=22, active_segment=21, line=38
|
||||||
|
browser_errors: 0
|
||||||
|
screenshot: /tmp/published-live-parameters-r14.png
|
||||||
|
screenshot_sha256: 7f5307c913d8bc4e60c2a31c29aacc9afd1b0d91fac4a4f22e8a64fc2ea37a1e
|
||||||
|
```
|
||||||
|
|
||||||
发布站点 r10 连续性验证:
|
发布站点 r10 连续性验证:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -270,6 +303,7 @@ panel_button_test_source 80782fc1f85f88beb48ec3dfaf638a93f2edb912e694840199dbff5
|
|||||||
switchkins_direct_test_output 24e6cb8ec1195a97f71294b49217db0cbc0d7c3ecfa4bc8aa244ba3921c816a7
|
switchkins_direct_test_output 24e6cb8ec1195a97f71294b49217db0cbc0d7c3ecfa4bc8aa244ba3921c816a7
|
||||||
panel_r10_output 9bddcd8b0219925e00598e081dc7a4ed0f4868e8822a21f0ccd08d8e9d355d45
|
panel_r10_output 9bddcd8b0219925e00598e081dc7a4ed0f4868e8822a21f0ccd08d8e9d355d45
|
||||||
pause_resume_browser_screenshot 9d1660fd043dd0628ff2989a3e139a3239acd6386d141657a5834c9d2f1bc5a7
|
pause_resume_browser_screenshot 9d1660fd043dd0628ff2989a3e139a3239acd6386d141657a5834c9d2f1bc5a7
|
||||||
|
live_parameters_browser_screenshot 7f5307c913d8bc4e60c2a31c29aacc9afd1b0d91fac4a4f22e8a64fc2ea37a1e
|
||||||
```
|
```
|
||||||
|
|
||||||
报告对应提交前工作树保留了既有 coverage 大文件和 LinuxCNC 子模块改动;本轮提交包含 WASM TP/IO/HOME authority、无 JS JOG 运动计算、前端 pending 投影、长程序快照性能修正、聚合测试和本报告。
|
报告对应提交前工作树保留了既有 coverage 大文件和 LinuxCNC 子模块改动;本轮提交包含 WASM TP/IO/HOME authority、无 JS JOG 运动计算、前端 pending 投影、长程序快照性能修正、聚合测试和本报告。
|
||||||
|
|||||||
@@ -309,5 +309,6 @@ if(EMSCRIPTEN)
|
|||||||
string(REPLACE "_sim_motion_step','_sim_motion_read_state" "_sim_motion_step','_sim_motion_set_profile','_sim_motion_set_lookahead','_sim_motion_set_feed_override','_sim_motion_peek','_sim_motion_read_state" _sim_module_link_flags "${_sim_module_link_flags}")
|
string(REPLACE "_sim_motion_step','_sim_motion_read_state" "_sim_motion_step','_sim_motion_set_profile','_sim_motion_set_lookahead','_sim_motion_set_feed_override','_sim_motion_peek','_sim_motion_read_state" _sim_module_link_flags "${_sim_module_link_flags}")
|
||||||
string(REPLACE "_sim_motion_state_hash','_sim_motion_canonical" "_sim_motion_state_hash','_sim_motion_plan_hash','_sim_motion_canonical" _sim_module_link_flags "${_sim_module_link_flags}")
|
string(REPLACE "_sim_motion_state_hash','_sim_motion_canonical" "_sim_motion_state_hash','_sim_motion_plan_hash','_sim_motion_canonical" _sim_module_link_flags "${_sim_module_link_flags}")
|
||||||
string(REPLACE "_sim_motion_init" "_sim_motion_init','_sim_linuxcnc_tp_size','_sim_linuxcnc_tp_queue_capacity','_sim_linuxcnc_tp_init','_sim_linuxcnc_tp_reset','_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_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_position','_sim_linuxcnc_tp_set_goal_position','_sim_linuxcnc_tp_add_line','_sim_linuxcnc_tp_add_circle','_sim_linuxcnc_tp_step','_sim_linuxcnc_tp_idle','_sim_linuxcnc_tp_read_state','_sim_linuxcnc_tp_queue_depth','_sim_linuxcnc_tp_queue_segment','_sim_linuxcnc_tp_restore_progress','_sim_linuxcnc_tp_state_hash','_sim_linuxcnc_tp_canonical" _sim_module_link_flags "${_sim_module_link_flags}")
|
string(REPLACE "_sim_motion_init" "_sim_motion_init','_sim_linuxcnc_tp_size','_sim_linuxcnc_tp_queue_capacity','_sim_linuxcnc_tp_init','_sim_linuxcnc_tp_reset','_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_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_position','_sim_linuxcnc_tp_set_goal_position','_sim_linuxcnc_tp_add_line','_sim_linuxcnc_tp_add_circle','_sim_linuxcnc_tp_step','_sim_linuxcnc_tp_idle','_sim_linuxcnc_tp_read_state','_sim_linuxcnc_tp_queue_depth','_sim_linuxcnc_tp_queue_segment','_sim_linuxcnc_tp_restore_progress','_sim_linuxcnc_tp_state_hash','_sim_linuxcnc_tp_canonical" _sim_module_link_flags "${_sim_module_link_flags}")
|
||||||
|
string(REPLACE "_sim_linuxcnc_tp_velocity_limit" "_sim_linuxcnc_tp_velocity_limit','_sim_linuxcnc_tp_velocity_mm_min" _sim_module_link_flags "${_sim_module_link_flags}")
|
||||||
set_target_properties(sim_core_module PROPERTIES LINK_FLAGS "${_sim_module_link_flags}")
|
set_target_properties(sim_core_module PROPERTIES LINK_FLAGS "${_sim_module_link_flags}")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ int32_t sim_linuxcnc_tp_set_axis_limit(sim_linuxcnc_tp *planner, int axis, doubl
|
|||||||
int32_t sim_linuxcnc_tp_jog(sim_linuxcnc_tp *planner, int axis, int direction, double increment, uint32_t tag);
|
int32_t sim_linuxcnc_tp_jog(sim_linuxcnc_tp *planner, int axis, int direction, double increment, uint32_t tag);
|
||||||
double sim_linuxcnc_tp_velocity_scale(const sim_linuxcnc_tp *planner);
|
double sim_linuxcnc_tp_velocity_scale(const sim_linuxcnc_tp *planner);
|
||||||
double sim_linuxcnc_tp_velocity_limit(const sim_linuxcnc_tp *planner);
|
double sim_linuxcnc_tp_velocity_limit(const sim_linuxcnc_tp *planner);
|
||||||
|
double sim_linuxcnc_tp_velocity_mm_min(const sim_linuxcnc_tp *planner);
|
||||||
double sim_linuxcnc_tp_jog_scale(const sim_linuxcnc_tp *planner);
|
double sim_linuxcnc_tp_jog_scale(const sim_linuxcnc_tp *planner);
|
||||||
double sim_linuxcnc_tp_jog_velocity(const sim_linuxcnc_tp *planner, int axis);
|
double sim_linuxcnc_tp_jog_velocity(const sim_linuxcnc_tp *planner, int axis);
|
||||||
double sim_linuxcnc_tp_feed_override(const sim_linuxcnc_tp *planner);
|
double sim_linuxcnc_tp_feed_override(const sim_linuxcnc_tp *planner);
|
||||||
|
|||||||
@@ -231,6 +231,10 @@ double sim_linuxcnc_tp_velocity_limit(const sim_linuxcnc_tp *planner) {
|
|||||||
return planner == NULL || !planner->initialized ? 0.0 : planner->tp.vLimit;
|
return planner == NULL || !planner->initialized ? 0.0 : planner->tp.vLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double sim_linuxcnc_tp_velocity_mm_min(const sim_linuxcnc_tp *planner) {
|
||||||
|
return planner == NULL || !planner->initialized ? 0.0 : planner->status.current_vel * 60.0;
|
||||||
|
}
|
||||||
|
|
||||||
double sim_linuxcnc_tp_jog_scale(const sim_linuxcnc_tp *planner) {
|
double sim_linuxcnc_tp_jog_scale(const sim_linuxcnc_tp *planner) {
|
||||||
return planner == NULL || !planner->initialized ? 0.0 : planner->jog_scale;
|
return planner == NULL || !planner->initialized ? 0.0 : planner->jog_scale;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ int main(void) {
|
|||||||
assert(planner != NULL);
|
assert(planner != NULL);
|
||||||
assert(sim_linuxcnc_tp_init(planner, 0.001, 35.0, 300.0) == 0);
|
assert(sim_linuxcnc_tp_init(planner, 0.001, 35.0, 300.0) == 0);
|
||||||
assert(sim_linuxcnc_tp_queue_capacity(planner) > 0);
|
assert(sim_linuxcnc_tp_queue_capacity(planner) > 0);
|
||||||
|
assert(sim_linuxcnc_tp_velocity_mm_min(planner) == 0.0);
|
||||||
assert(sim_linuxcnc_tp_set_position(planner, &origin) == 0);
|
assert(sim_linuxcnc_tp_set_position(planner, &origin) == 0);
|
||||||
assert(sim_linuxcnc_tp_set_profile(planner, 0, 0.0) == 0);
|
assert(sim_linuxcnc_tp_set_profile(planner, 0, 0.0) == 0);
|
||||||
assert(sim_linuxcnc_tp_add_line(planner, &first, 20.0, 300.0, 16, 1) == 0);
|
assert(sim_linuxcnc_tp_add_line(planner, &first, 20.0, 300.0, 16, 1) == 0);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { SIM_CASE_CATALOG } from "../../workspace/sim-case-catalog.mjs";
|
|||||||
import { loadSimulationCase, toWorkerConfiguration } from "../../workspace/sim-case-loader.mjs";
|
import { loadSimulationCase, toWorkerConfiguration } from "../../workspace/sim-case-loader.mjs";
|
||||||
import { axisIcon } from "./sourceAssets";
|
import { axisIcon } from "./sourceAssets";
|
||||||
import { AxisOperationController } from "../../app/axis-operation-controller.mjs";
|
import { AxisOperationController } from "../../app/axis-operation-controller.mjs";
|
||||||
import { XyzacDiagnostics, XyzacProgramPane, XyzacTrtPanel } from "./XyzacTrtPanel";
|
import { XyzacDiagnostics, XyzacLiveParameters, XyzacProgramPane, XyzacTrtPanel } from "./XyzacTrtPanel";
|
||||||
import { XYZAC_CASE, projectXyzacPanel, type XyzacOperationTrace } from "./xyzac-panel-contract";
|
import { XYZAC_CASE, projectXyzacPanel, type XyzacOperationTrace } from "./xyzac-panel-contract";
|
||||||
|
|
||||||
export const SELECTED_XYZAC_CASE = XYZAC_CASE;
|
export const SELECTED_XYZAC_CASE = XYZAC_CASE;
|
||||||
@@ -472,6 +472,7 @@ export function App() {
|
|||||||
<section className="axis-case-strip" data-axis-feature="AXIS-EDIT-03"><label>Simulation case<select data-axis-feature="AXIS-EDIT-03" value={casePath} onChange={(event) => setCasePath(event.target.value)}>{SIM_CASE_CATALOG.filter((entry) => entry.path === SELECTED_XYZAC_CASE).map((entry) => <option key={entry.path} value={entry.path}>{entry.label} | XYZAC TRT</option>)}</select></label><TextButton feature="AXIS-EDIT-03" onClick={loadCase} disabled={!gates.load.enabled || programLoading}>Load case</TextButton><TextButton feature="AXIS-EDIT-03" onClick={loadPackagedProgram} disabled={!gates.load.enabled || programLoading}>Load 3D_Chips.ngc</TextButton><span className={`case-status${programLoading ? " loading" : ""}`} role="status" aria-live="polite" aria-busy={programLoading}>{programLoading ? "PROGRAM_LOAD pending: LinuxCNC WASM preview in progress..." : caseStatus}</span>{previewProgress && <div className={`preview-load-indicator ${previewProgress.phase}`} role="status" aria-live="polite" aria-label="Toolpath preview loading progress"><span title={previewProgress.detail}>{previewProgress.detail}</span><progress max="100" value={previewProgress.percent}>{previewProgress.percent}%</progress><output>{previewProgress.percent}%</output></div>}<span className="command-status" role="status">{commandStatus}</span></section>
|
<section className="axis-case-strip" data-axis-feature="AXIS-EDIT-03"><label>Simulation case<select data-axis-feature="AXIS-EDIT-03" value={casePath} onChange={(event) => setCasePath(event.target.value)}>{SIM_CASE_CATALOG.filter((entry) => entry.path === SELECTED_XYZAC_CASE).map((entry) => <option key={entry.path} value={entry.path}>{entry.label} | XYZAC TRT</option>)}</select></label><TextButton feature="AXIS-EDIT-03" onClick={loadCase} disabled={!gates.load.enabled || programLoading}>Load case</TextButton><TextButton feature="AXIS-EDIT-03" onClick={loadPackagedProgram} disabled={!gates.load.enabled || programLoading}>Load 3D_Chips.ngc</TextButton><span className={`case-status${programLoading ? " loading" : ""}`} role="status" aria-live="polite" aria-busy={programLoading}>{programLoading ? "PROGRAM_LOAD pending: LinuxCNC WASM preview in progress..." : caseStatus}</span>{previewProgress && <div className={`preview-load-indicator ${previewProgress.phase}`} role="status" aria-live="polite" aria-label="Toolpath preview loading progress"><span title={previewProgress.detail}>{previewProgress.detail}</span><progress max="100" value={previewProgress.percent}>{previewProgress.percent}%</progress><output>{previewProgress.percent}%</output></div>}<span className="command-status" role="status">{commandStatus}</span></section>
|
||||||
<MachineSequence wasmReady={wasmSnapshotReady} isEstop={isEstop} machineOn={machineOn} allHomed={allHomed} programLoaded={programLoaded} programRunning={programRunning} programLoading={programLoading} canPower={gates.power.enabled} canHome={gates.home.enabled} canRun={gates.run.enabled} onEstop={emergencyStop} onRelease={() => { void releaseEstop().catch(() => undefined); }} onPower={powerOn} onHomeAll={homeAll} onRun={runProgram} onSequence={runStartupSequence} />
|
<MachineSequence wasmReady={wasmSnapshotReady} isEstop={isEstop} machineOn={machineOn} allHomed={allHomed} programLoaded={programLoaded} programRunning={programRunning} programLoading={programLoading} canPower={gates.power.enabled} canHome={gates.home.enabled} canRun={gates.run.enabled} onEstop={emergencyStop} onRelease={() => { void releaseEstop().catch(() => undefined); }} onPower={powerOn} onHomeAll={homeAll} onRun={runProgram} onSequence={runStartupSequence} />
|
||||||
<XyzacTrtPanel view={xyzacView} command={xyzacCommand} />
|
<XyzacTrtPanel view={xyzacView} command={xyzacCommand} />
|
||||||
|
<XyzacLiveParameters snapshot={state.snapshot} />
|
||||||
<section className="axis-top-pane" data-axis-feature="AXIS-UI-06"><div className="axis-notebook left-notebook" data-axis-feature="AXIS-UI-06"><div className="notebook-tabs" role="tablist" aria-label="Manual, MDI and diagnostics controls"><button id="axis-tab-manual" type="button" role="tab" aria-controls="axis-panel-manual" aria-selected={leftTab === "manual"} className={leftTab === "manual" ? "active" : ""} onClick={enterManualMode}>Manual Control [F3]</button><button id="axis-tab-mdi" type="button" role="tab" aria-controls="axis-panel-mdi" aria-selected={leftTab === "mdi"} className={leftTab === "mdi" ? "active" : ""} onClick={() => setLeftTab("mdi")}>MDI [F5]</button><button id="axis-tab-diagnostics" type="button" role="tab" aria-controls="axis-panel-diagnostics" aria-selected={leftTab === "diagnostics"} className={leftTab === "diagnostics" ? "active" : ""} onClick={() => setLeftTab("diagnostics")}>Diagnostics</button></div><div id="axis-panel-manual" role="tabpanel" aria-labelledby="axis-tab-manual" hidden={leftTab !== "manual"}><ManualControl action={execute} canJog={manualReady && gates.jog.enabled} canHome={gates.home.enabled} canSpindle={manualReady && Boolean(gates.spindle?.enabled)} canCoolant={wasmSnapshotReady && Boolean(gates.coolant?.enabled)} canTool={manualReady && Boolean(gates.tool?.enabled)} canProbe={manualReady && Boolean(gates.probe?.enabled)} canTouchOff={manualReady && Boolean(gates.touchOff?.enabled)} canToolTouchOff={manualReady && Boolean(gates.toolTouchOff?.enabled)} onHomeAll={homeAll} selectedAxis={selectedAxis} setSelectedAxis={setSelectedAxis} spindleOn={spindleOn} spindleBrake={spindleBrake} coolant={coolant} onSpindle={onSpindle} onCoolant={onCoolant} jogScale={Number.isFinite(sourceJogScale) ? sourceJogScale : null} onJogScale={(event) => void dispatch("SET_JOG_SCALE", { percent: Number(event.target.value) })} devices={state.devices ?? {}} /></div><div id="axis-panel-mdi" role="tabpanel" aria-labelledby="axis-tab-mdi" hidden={leftTab !== "mdi"}><MdiControl action={execute} ready={manualReady && gates.mdi.enabled} /></div><div id="axis-panel-diagnostics" role="tabpanel" aria-labelledby="axis-tab-diagnostics" hidden={leftTab !== "diagnostics"}><DiagnosticsPanel state={state} gates={gates} /><XyzacDiagnostics view={xyzacView} operations={operationTrace} alarms={state.alarms} error={state.error} /></div></div><div className="axis-notebook right-notebook" data-axis-feature="AXIS-UI-06"><div className="notebook-tabs" role="tablist" aria-label="Preview and DRO"><button id="axis-tab-preview" type="button" role="tab" aria-controls="axis-panel-preview" aria-selected={rightTab === "preview"} className={rightTab === "preview" ? "active" : ""} onClick={() => setRightTab("preview")}>Preview</button><button id="axis-tab-dro" type="button" role="tab" aria-controls="axis-panel-dro" aria-selected={rightTab === "dro"} className={rightTab === "dro" ? "active" : ""} onClick={() => setRightTab("dro")}>DRO</button></div><div id="axis-panel-preview" role="tabpanel" aria-labelledby="axis-tab-preview" className="axis-preview" hidden={rightTab !== "preview"}><XyzacToolPreviewFromSnapshot snapshot={programLoading ? null : state.snapshot} view={view} zoom={zoom} rotateMode={rotateMode} showPath={layers.path} showLive={layers.live} showLimits={layers.limits} showVelocity={layers.velocity} showOffsets={layers.offsets} showExtents={layers.extents} /></div><div id="axis-panel-dro" role="tabpanel" aria-labelledby="axis-tab-dro" hidden={rightTab !== "dro"}><DroView axes={axes} state={state} coordType={coordType} displayType={displayType} metric={metric} setCoordType={setCoordType} setDisplayType={setDisplayType} setMetric={setMetric} /></div></div></section>
|
<section className="axis-top-pane" data-axis-feature="AXIS-UI-06"><div className="axis-notebook left-notebook" data-axis-feature="AXIS-UI-06"><div className="notebook-tabs" role="tablist" aria-label="Manual, MDI and diagnostics controls"><button id="axis-tab-manual" type="button" role="tab" aria-controls="axis-panel-manual" aria-selected={leftTab === "manual"} className={leftTab === "manual" ? "active" : ""} onClick={enterManualMode}>Manual Control [F3]</button><button id="axis-tab-mdi" type="button" role="tab" aria-controls="axis-panel-mdi" aria-selected={leftTab === "mdi"} className={leftTab === "mdi" ? "active" : ""} onClick={() => setLeftTab("mdi")}>MDI [F5]</button><button id="axis-tab-diagnostics" type="button" role="tab" aria-controls="axis-panel-diagnostics" aria-selected={leftTab === "diagnostics"} className={leftTab === "diagnostics" ? "active" : ""} onClick={() => setLeftTab("diagnostics")}>Diagnostics</button></div><div id="axis-panel-manual" role="tabpanel" aria-labelledby="axis-tab-manual" hidden={leftTab !== "manual"}><ManualControl action={execute} canJog={manualReady && gates.jog.enabled} canHome={gates.home.enabled} canSpindle={manualReady && Boolean(gates.spindle?.enabled)} canCoolant={wasmSnapshotReady && Boolean(gates.coolant?.enabled)} canTool={manualReady && Boolean(gates.tool?.enabled)} canProbe={manualReady && Boolean(gates.probe?.enabled)} canTouchOff={manualReady && Boolean(gates.touchOff?.enabled)} canToolTouchOff={manualReady && Boolean(gates.toolTouchOff?.enabled)} onHomeAll={homeAll} selectedAxis={selectedAxis} setSelectedAxis={setSelectedAxis} spindleOn={spindleOn} spindleBrake={spindleBrake} coolant={coolant} onSpindle={onSpindle} onCoolant={onCoolant} jogScale={Number.isFinite(sourceJogScale) ? sourceJogScale : null} onJogScale={(event) => void dispatch("SET_JOG_SCALE", { percent: Number(event.target.value) })} devices={state.devices ?? {}} /></div><div id="axis-panel-mdi" role="tabpanel" aria-labelledby="axis-tab-mdi" hidden={leftTab !== "mdi"}><MdiControl action={execute} ready={manualReady && gates.mdi.enabled} /></div><div id="axis-panel-diagnostics" role="tabpanel" aria-labelledby="axis-tab-diagnostics" hidden={leftTab !== "diagnostics"}><DiagnosticsPanel state={state} gates={gates} /><XyzacDiagnostics view={xyzacView} operations={operationTrace} alarms={state.alarms} error={state.error} /></div></div><div className="axis-notebook right-notebook" data-axis-feature="AXIS-UI-06"><div className="notebook-tabs" role="tablist" aria-label="Preview and DRO"><button id="axis-tab-preview" type="button" role="tab" aria-controls="axis-panel-preview" aria-selected={rightTab === "preview"} className={rightTab === "preview" ? "active" : ""} onClick={() => setRightTab("preview")}>Preview</button><button id="axis-tab-dro" type="button" role="tab" aria-controls="axis-panel-dro" aria-selected={rightTab === "dro"} className={rightTab === "dro" ? "active" : ""} onClick={() => setRightTab("dro")}>DRO</button></div><div id="axis-panel-preview" role="tabpanel" aria-labelledby="axis-tab-preview" className="axis-preview" hidden={rightTab !== "preview"}><XyzacToolPreviewFromSnapshot snapshot={programLoading ? null : state.snapshot} view={view} zoom={zoom} rotateMode={rotateMode} showPath={layers.path} showLive={layers.live} showLimits={layers.limits} showVelocity={layers.velocity} showOffsets={layers.offsets} showExtents={layers.extents} /></div><div id="axis-panel-dro" role="tabpanel" aria-labelledby="axis-tab-dro" hidden={rightTab !== "dro"}><DroView axes={axes} state={state} coordType={coordType} displayType={displayType} metric={metric} setCoordType={setCoordType} setDisplayType={setDisplayType} setMetric={setMetric} /></div></div></section>
|
||||||
<section className="axis-metrics" data-axis-feature="AXIS-VIEW-03"><div className="axis-slider"><label>Jog Speed:<output>{jogScalePercent === null ? "-" : `${jogScalePercent}%`}</output><input type="range" min="6" max="100" value={jogScalePercent ?? 6} onChange={(event) => void dispatch("SET_JOG_SCALE", { percent: Number(event.target.value) })} disabled={!gates.jog.enabled || jogScalePercent === null} aria-label="Jog speed" /></label><label>Feed Override:<output>{feedOverridePercent === null ? "-" : `${feedOverridePercent}%`}</output><input type="range" min="0" max="200" step="1" value={feedOverridePercent ?? 0} onChange={requestFeedOverride} disabled={!gates.override.enabled || programLoading || feedOverridePercent === null} aria-label="Feed override" aria-valuetext={feedOverridePercent === null ? "WASM state pending" : `${feedOverridePercent}%`} data-axis-feed-override={feedOverridePercent ?? ""} /></label><label>Spindle Override:<output>{spindleOverridePercent === null ? "-" : `${spindleOverridePercent}%`}</output><input type="range" min="0" max="200" value={spindleOverridePercent ?? 0} onChange={(event) => void dispatch("SET_SPINDLE_OVERRIDE", { spindle: 0, scale: Number(event.target.value) / 100 })} disabled={!gates.override.enabled || spindleOverridePercent === null} aria-label="Spindle override" /></label><label>Max Velocity:<output>{maxVelocityPercent === null ? "-" : `${maxVelocityPercent}%`}</output><input type="range" min="6" max="100" value={maxVelocityPercent ?? 6} onChange={(event) => void dispatch("SET_MAX_VELOCITY", { percent: Number(event.target.value) })} disabled={!gates.override.enabled || maxVelocityPercent === null} aria-label="Max velocity" /></label></div><div className="axis-layer-controls" aria-label="Preview layers">{(["path", "live", "limits", "velocity", "offsets", "extents"] as const).map((layer) => <label key={layer}><input type="checkbox" data-axis-feature="AXIS-VIEW-03" checked={layers[layer]} onChange={(event) => setLayers((current) => ({ ...current, [layer]: event.target.checked }))} /> {layer}</label>)}</div></section>
|
<section className="axis-metrics" data-axis-feature="AXIS-VIEW-03"><div className="axis-slider"><label>Jog Speed:<output>{jogScalePercent === null ? "-" : `${jogScalePercent}%`}</output><input type="range" min="6" max="100" value={jogScalePercent ?? 6} onChange={(event) => void dispatch("SET_JOG_SCALE", { percent: Number(event.target.value) })} disabled={!gates.jog.enabled || jogScalePercent === null} aria-label="Jog speed" /></label><label>Feed Override:<output>{feedOverridePercent === null ? "-" : `${feedOverridePercent}%`}</output><input type="range" min="0" max="200" step="1" value={feedOverridePercent ?? 0} onChange={requestFeedOverride} disabled={!gates.override.enabled || programLoading || feedOverridePercent === null} aria-label="Feed override" aria-valuetext={feedOverridePercent === null ? "WASM state pending" : `${feedOverridePercent}%`} data-axis-feed-override={feedOverridePercent ?? ""} /></label><label>Spindle Override:<output>{spindleOverridePercent === null ? "-" : `${spindleOverridePercent}%`}</output><input type="range" min="0" max="200" value={spindleOverridePercent ?? 0} onChange={(event) => void dispatch("SET_SPINDLE_OVERRIDE", { spindle: 0, scale: Number(event.target.value) / 100 })} disabled={!gates.override.enabled || spindleOverridePercent === null} aria-label="Spindle override" /></label><label>Max Velocity:<output>{maxVelocityPercent === null ? "-" : `${maxVelocityPercent}%`}</output><input type="range" min="6" max="100" value={maxVelocityPercent ?? 6} onChange={(event) => void dispatch("SET_MAX_VELOCITY", { percent: Number(event.target.value) })} disabled={!gates.override.enabled || maxVelocityPercent === null} aria-label="Max velocity" /></label></div><div className="axis-layer-controls" aria-label="Preview layers">{(["path", "live", "limits", "velocity", "offsets", "extents"] as const).map((layer) => <label key={layer}><input type="checkbox" data-axis-feature="AXIS-VIEW-03" checked={layers[layer]} onChange={(event) => setLayers((current) => ({ ...current, [layer]: event.target.checked }))} /> {layer}</label>)}</div></section>
|
||||||
<XyzacProgramPane status={visibleProgramStatus} currentLine={visibleCurrentLine} sourceHash={programLoading ? "" : String(state.program?.source_hash ?? "")}><ProgramPane text={programText} setText={setProgramText} activeGcodes={activeGcodes} executionTrace={visibleExecutionTrace} programStatus={visibleProgramStatus} onLoad={loadProgram} onRun={runProgram} onStep={() => execute("PROGRAM_STEP")} canLoad={gates.load.enabled && !programLoading} canRun={gates.run.enabled && !programLoading} canStep={gates.step.enabled && !programLoading} currentLine={visibleCurrentLine} executionLine={visibleExecutionLine} executionLineText={visibleExecutionLineText} diagnostic={state.error?.code ?? null} /></XyzacProgramPane>
|
<XyzacProgramPane status={visibleProgramStatus} currentLine={visibleCurrentLine} sourceHash={programLoading ? "" : String(state.program?.source_hash ?? "")}><ProgramPane text={programText} setText={setProgramText} activeGcodes={activeGcodes} executionTrace={visibleExecutionTrace} programStatus={visibleProgramStatus} onLoad={loadProgram} onRun={runProgram} onStep={() => execute("PROGRAM_STEP")} canLoad={gates.load.enabled && !programLoading} canRun={gates.run.enabled && !programLoading} canStep={gates.step.enabled && !programLoading} currentLine={visibleCurrentLine} executionLine={visibleExecutionLine} executionLineText={visibleExecutionLineText} diagnostic={state.error?.code ?? null} /></XyzacProgramPane>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { XYZAC_KINEMATICS, type XyzacOperationTrace, type XyzacPanelView } from "./xyzac-panel-contract";
|
import { XYZAC_KINEMATICS, type XyzacOperationTrace, type XyzacPanelView } from "./xyzac-panel-contract";
|
||||||
|
import type { SimSnapshotV1 } from "./protocol";
|
||||||
|
|
||||||
type Command = (kind: string, payload?: Record<string, unknown>, action?: string) => void;
|
type Command = (kind: string, payload?: Record<string, unknown>, action?: string) => void;
|
||||||
|
|
||||||
@@ -34,6 +35,26 @@ export function XyzacProgramPane({ status, currentLine, sourceHash, children }:
|
|||||||
return <section className="xyzac-program" aria-label="XYZAC program"><header><strong>XYZAC Program</strong><span>{status}</span><span>Line {currentLine ?? "-"}</span><code>{sourceHash?.slice(0, 18) ?? "no source"}</code></header>{children}</section>;
|
return <section className="xyzac-program" aria-label="XYZAC program"><header><strong>XYZAC Program</strong><span>{status}</span><span>Line {currentLine ?? "-"}</span><code>{sourceHash?.slice(0, 18) ?? "no source"}</code></header>{children}</section>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function XyzacLiveParameters({ snapshot }: { snapshot: SimSnapshotV1 | null }) {
|
||||||
|
const ready = snapshot?.motion_authority === "wasm" && Boolean(snapshot?.task_authority);
|
||||||
|
const motion = (snapshot?.motion ?? {}) as Record<string, unknown>;
|
||||||
|
const program = snapshot?.program ?? null;
|
||||||
|
const number = (value: unknown, digits = 2, unit = "") => ready && typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(digits)}${unit ? ` ${unit}` : ""}` : "-";
|
||||||
|
const integer = (value: unknown) => ready && Number.isSafeInteger(value) ? String(value) : "-";
|
||||||
|
const wasmState = (snapshot?.wasm_motion as { state?: Record<string, unknown> } | undefined)?.state;
|
||||||
|
return <section className="xyzac-live-parameters" data-axis-feature="AXIS-LIVE-PARAMETERS" data-xyzac-parameter-source={ready ? "linuxcnc-wasm" : "pending"} aria-label="Live machining parameters">
|
||||||
|
<div className="xyzac-section-title"><strong>Live parameters</strong><span>{ready ? "LinuxCNC WASM" : "WASM PENDING"}</span></div>
|
||||||
|
<div className="xyzac-parameter-grid">
|
||||||
|
<div><b>Spindle</b><output>{number(motion.spindle_speed_rpm, 1, "rpm")}</output><small>Cmd {number(motion.spindle_command_rpm, 1, "rpm")} / S {number(program?.speed, 1, "rpm")}</small></div>
|
||||||
|
<div><b>Feed actual</b><output>{number(motion.velocity_mm_min, 1, "mm/min")}</output><small>Program {number(motion.feed_command_mm_min, 1, "mm/min")}</small></div>
|
||||||
|
<div><b>TP velocity</b><output>{number(motion.velocity, 3, "mm/s")}</output><small>Limit {number(motion.max_velocity, 3, "mm/s")}</small></div>
|
||||||
|
<div><b>Overrides</b><output>F {number(typeof motion.feed_override === "number" ? motion.feed_override * 100 : Number.NaN, 0, "%")}</output><small>S {number(typeof motion.spindle_override === "number" ? motion.spindle_override * 100 : Number.NaN, 0, "%")}</small></div>
|
||||||
|
<div><b>Planner</b><output>{integer(wasmState?.queue_depth)} queued</output><small>Segment {integer(wasmState?.active_id)}</small></div>
|
||||||
|
<div><b>Execution</b><output>Line {integer(program?.execution_line ?? program?.current_line)}</output><small>{ready ? String(program?.program_status ?? "-") : "-"}</small></div>
|
||||||
|
</div>
|
||||||
|
</section>;
|
||||||
|
}
|
||||||
|
|
||||||
export function XyzacDiagnostics({ view, operations, alarms, error }: { view: XyzacPanelView; operations: XyzacOperationTrace[]; alarms: Array<Record<string, unknown>>; error: { code: string; message?: string } | null }) {
|
export function XyzacDiagnostics({ view, operations, alarms, error }: { view: XyzacPanelView; operations: XyzacOperationTrace[]; alarms: Array<Record<string, unknown>>; error: { code: string; message?: string } | null }) {
|
||||||
const disabled = Object.entries(view.gates).filter(([, gate]) => gate.enabled === false);
|
const disabled = Object.entries(view.gates).filter(([, gate]) => gate.enabled === false);
|
||||||
return <section className="xyzac-diagnostics" aria-label="XYZAC diagnostics">
|
return <section className="xyzac-diagnostics" aria-label="XYZAC diagnostics">
|
||||||
|
|||||||
@@ -27,11 +27,18 @@ export type MotionSnapshot = {
|
|||||||
on_any_limit?: boolean;
|
on_any_limit?: boolean;
|
||||||
fault?: string | null;
|
fault?: string | null;
|
||||||
velocity_limit?: number;
|
velocity_limit?: number;
|
||||||
|
velocity_mm_min?: number;
|
||||||
|
feed_command_mm_min?: number;
|
||||||
velocity_scale?: number;
|
velocity_scale?: number;
|
||||||
jog_scale?: number;
|
jog_scale?: number;
|
||||||
jog_velocity_linear?: number;
|
jog_velocity_linear?: number;
|
||||||
jog_velocity_angular?: number;
|
jog_velocity_angular?: number;
|
||||||
spindle_override?: number;
|
spindle_override?: number;
|
||||||
|
spindle_speed_rpm?: number;
|
||||||
|
spindle_command_rpm?: number;
|
||||||
|
spindle_enabled?: boolean;
|
||||||
|
spindle_at_speed?: boolean;
|
||||||
|
max_velocity?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TrajectorySnapshot = {
|
export type TrajectorySnapshot = {
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ function projection(result: Record<string, unknown>, message: MessageEnvelope, p
|
|||||||
overrides: (result.overrides as SimSnapshotV1["overrides"]) ?? previous.snapshot.overrides,
|
overrides: (result.overrides as SimSnapshotV1["overrides"]) ?? previous.snapshot.overrides,
|
||||||
motion: {
|
motion: {
|
||||||
...previous.snapshot.motion,
|
...previous.snapshot.motion,
|
||||||
|
...(result.motion as SimSnapshotV1["motion"] | undefined),
|
||||||
// Keep the XYZAC axis vector intact on every motion delta. Older
|
// Keep the XYZAC axis vector intact on every motion delta. Older
|
||||||
// projection code rebuilt only XYZ, which made A/C disappear from
|
// projection code rebuilt only XYZ, which made A/C disappear from
|
||||||
// consumers that read the canonical motion array.
|
// consumers that read the canonical motion array.
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ button { color: inherit; }
|
|||||||
.xyzac-position-grid output { overflow: hidden; font-size: 13px; text-overflow: ellipsis; }
|
.xyzac-position-grid output { overflow: hidden; font-size: 13px; text-overflow: ellipsis; }
|
||||||
.xyzac-position-grid small { overflow: hidden; color: #666; font: 9px ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
.xyzac-position-grid small { overflow: hidden; color: #666; font: 9px ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.xyzac-offset-row { display: flex; gap: 12px; margin-top: 5px; color: #555; font: 10px ui-monospace, monospace; }
|
.xyzac-offset-row { display: flex; gap: 12px; margin-top: 5px; color: #555; font: 10px ui-monospace, monospace; }
|
||||||
|
.xyzac-live-parameters { margin: 0 5px 5px; padding: 5px 8px; border: 1px solid var(--border-dark); background: #dedede; }
|
||||||
|
.xyzac-live-parameters .xyzac-section-title { margin: 0 0 4px; }
|
||||||
|
.xyzac-parameter-grid { display: grid; grid-template-columns: repeat(6, minmax(120px, 1fr)); gap: 5px; }
|
||||||
|
.xyzac-parameter-grid > div { display: grid; grid-template-columns: auto 1fr; align-items: baseline; gap: 2px 6px; min-width: 0; padding: 4px 6px; border-left: 2px solid #7894aa; background: #f7f7f7; }
|
||||||
|
.xyzac-parameter-grid b { color: #36434c; font-size: 11px; }
|
||||||
|
.xyzac-parameter-grid output { grid-column: 2; color: #111; font-size: 13px; white-space: nowrap; }
|
||||||
|
.xyzac-parameter-grid small { grid-column: 1 / -1; overflow: hidden; color: #666; font: 10px ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.xyzac-program { display: contents; }
|
.xyzac-program { display: contents; }
|
||||||
.xyzac-program > header { display: flex; align-items: center; gap: 14px; min-height: 25px; margin: 0 5px; padding: 3px 8px; border: 1px solid #888; border-bottom: 0; background: #d4d4d4; font-size: 11px; }
|
.xyzac-program > header { display: flex; align-items: center; gap: 14px; min-height: 25px; margin: 0 5px; padding: 3px 8px; border: 1px solid #888; border-bottom: 0; background: #d4d4d4; font-size: 11px; }
|
||||||
.xyzac-program > header code { margin-left: auto; color: #4d4d4d; }
|
.xyzac-program > header code { margin-left: auto; color: #4d4d4d; }
|
||||||
@@ -180,6 +187,8 @@ output { font: 12px ui-monospace, monospace; text-align: right; }
|
|||||||
.axis-metrics { padding: 4px 9px; border-top: 1px solid #999; background: #cecece; }
|
.axis-metrics { padding: 4px 9px; border-top: 1px solid #999; background: #cecece; }
|
||||||
.axis-slider { display: grid; grid-template-columns: repeat(3, minmax(170px, 1fr)); gap: 12px; }
|
.axis-slider { display: grid; grid-template-columns: repeat(3, minmax(170px, 1fr)); gap: 12px; }
|
||||||
.axis-slider label { grid-template-columns: auto 48px; }
|
.axis-slider label { grid-template-columns: auto 48px; }
|
||||||
|
@media (max-width: 900px) { .xyzac-parameter-grid { grid-template-columns: repeat(3, minmax(120px, 1fr)); } }
|
||||||
|
@media (max-width: 560px) { .xyzac-parameter-grid { grid-template-columns: repeat(2, minmax(120px, 1fr)); } }
|
||||||
|
|
||||||
.axis-program-pane { display: grid; grid-template-columns: minmax(0, 1fr) 230px; grid-template-rows: minmax(0, 1fr); flex: 0 0 430px; height: 430px; min-height: 430px; margin: 0 5px 5px; border: 2px solid #777; background: #f7f7f7; }
|
.axis-program-pane { display: grid; grid-template-columns: minmax(0, 1fr) 230px; grid-template-rows: minmax(0, 1fr); flex: 0 0 430px; height: 430px; min-height: 430px; margin: 0 5px 5px; border: 2px solid #777; background: #f7f7f7; }
|
||||||
.axis-diagnostics { display: grid; gap: 8px; padding: 10px; min-height: 190px; overflow: auto; background: #f7f7f7; font-size: 12px; }
|
.axis-diagnostics { display: grid; gap: 8px; padding: 10px; min-height: 190px; overflow: auto; background: #f7f7f7; font-size: 12px; }
|
||||||
|
|||||||
@@ -196,6 +196,13 @@ export class SimSession {
|
|||||||
source_hash: program.source_hash,
|
source_hash: program.source_hash,
|
||||||
state_hash: program.state_hash,
|
state_hash: program.state_hash,
|
||||||
active_gcodes: program.active_gcodes,
|
active_gcodes: program.active_gcodes,
|
||||||
|
feed: program.feed,
|
||||||
|
speed: program.speed,
|
||||||
|
units: program.units,
|
||||||
|
distance_mode: program.distance_mode,
|
||||||
|
motion_control: program.motion_control,
|
||||||
|
motion_control_mode: program.motion_control_mode,
|
||||||
|
path_tolerance: program.path_tolerance,
|
||||||
} : null;
|
} : null;
|
||||||
const trace = Array.isArray(statePayload.motion_trace) ? statePayload.motion_trace : [];
|
const trace = Array.isArray(statePayload.motion_trace) ? statePayload.motion_trace : [];
|
||||||
const motionSample = motionDelta ? (trace.at(-1) ?? {
|
const motionSample = motionDelta ? (trace.at(-1) ?? {
|
||||||
@@ -239,12 +246,14 @@ export class SimSession {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
wasmMotionProjection() {
|
wasmMotionProjection(program = null) {
|
||||||
const wasmMotion = this.wasmMotion.read();
|
const wasmMotion = this.wasmMotion.read();
|
||||||
const position = wasmMotion.status === "verified" ? wasmMotion.state.position : { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0, u: 0, v: 0, w: 0 };
|
const position = wasmMotion.status === "verified" ? wasmMotion.state.position : { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0, u: 0, v: 0, w: 0 };
|
||||||
const home = this.wasmHomeLimit.read();
|
const home = this.wasmHomeLimit.read();
|
||||||
const io = this.xyzacIo?.canonical?.();
|
const io = this.xyzacIo?.canonical?.();
|
||||||
const ioState = io && typeof io === "object" && io.status !== "blocked" ? io : null;
|
const ioState = io && typeof io === "object" && io.status !== "blocked" ? io : null;
|
||||||
|
const sourceProgram = program ?? this.xyzacProgram?.snapshot();
|
||||||
|
const spindle = ioState?.spindles?.[0] ?? null;
|
||||||
const kinematics = this.xyzacKinematics?.snapshot?.();
|
const kinematics = this.xyzacKinematics?.snapshot?.();
|
||||||
const plannerJoints = wasmMotion.status === "verified" && Array.isArray(wasmMotion.state.joint_commanded) && wasmMotion.state.joint_commanded.length === 5 ? [...wasmMotion.state.joint_commanded] : null;
|
const plannerJoints = wasmMotion.status === "verified" && Array.isArray(wasmMotion.state.joint_commanded) && wasmMotion.state.joint_commanded.length === 5 ? [...wasmMotion.state.joint_commanded] : null;
|
||||||
const kinematicsJoints = plannerJoints ?? (kinematics?.joints?.length === 5 ? [...kinematics.joints] : null);
|
const kinematicsJoints = plannerJoints ?? (kinematics?.joints?.length === 5 ? [...kinematics.joints] : null);
|
||||||
@@ -255,8 +264,14 @@ export class SimSession {
|
|||||||
axes: { x: position.x, y: position.y, z: position.z },
|
axes: { x: position.x, y: position.y, z: position.z },
|
||||||
joints: kinematicsJoints ?? (home.status === "verified" && Array.isArray(home.state.joints) ? [...home.state.joints] : [position.x, position.y, position.z, position.a ?? 0, position.c ?? 0]),
|
joints: kinematicsJoints ?? (home.status === "verified" && Array.isArray(home.state.joints) ? [...home.state.joints] : [position.x, position.y, position.z, position.a ?? 0, position.c ?? 0]),
|
||||||
velocity: wasmMotion.status === "verified" ? wasmMotion.state.velocity : 0,
|
velocity: wasmMotion.status === "verified" ? wasmMotion.state.velocity : 0,
|
||||||
|
velocity_mm_min: Number(wasmMotion.state?.velocity_mm_min ?? 0),
|
||||||
|
feed_command_mm_min: Number(sourceProgram?.feed ?? 0),
|
||||||
feed_override: Number(wasmMotion.state?.feed_override ?? 1),
|
feed_override: Number(wasmMotion.state?.feed_override ?? 1),
|
||||||
spindle_override: Number(ioState?.spindles?.[0]?.override ?? 1),
|
spindle_override: Number(ioState?.spindles?.[0]?.override ?? 1),
|
||||||
|
spindle_speed_rpm: Number(spindle?.speed_rpm ?? 0),
|
||||||
|
spindle_command_rpm: Number(spindle?.command_rpm ?? 0),
|
||||||
|
spindle_enabled: Boolean(spindle?.enabled),
|
||||||
|
spindle_at_speed: Boolean(spindle?.at_speed),
|
||||||
velocity_scale: Number(wasmMotion.state?.velocity_scale ?? 1),
|
velocity_scale: Number(wasmMotion.state?.velocity_scale ?? 1),
|
||||||
jog_scale: Number(wasmMotion.state?.jog_scale ?? 1),
|
jog_scale: Number(wasmMotion.state?.jog_scale ?? 1),
|
||||||
jog_velocity_linear: Number(wasmMotion.state?.jog_velocity_linear ?? 0),
|
jog_velocity_linear: Number(wasmMotion.state?.jog_velocity_linear ?? 0),
|
||||||
@@ -290,6 +305,17 @@ export class SimSession {
|
|||||||
return { ...unsigned, state_hash: fnv1a64(unsigned) };
|
return { ...unsigned, state_hash: fnv1a64(unsigned) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncXyzacProgramSpindle(program) {
|
||||||
|
if (!this.xyzacIo || !program || typeof program !== "object") return { status: "skipped" };
|
||||||
|
const mode = Number(program.spindle_mode ?? 5);
|
||||||
|
const rpm = Number(program.speed ?? 0);
|
||||||
|
const enabled = (mode === 3 || mode === 4) && Number.isFinite(rpm) && rpm > 0;
|
||||||
|
const signedRpm = mode === 4 ? -rpm : rpm;
|
||||||
|
const current = this.xyzacIo.canonical()?.spindles?.[0];
|
||||||
|
if (current && Boolean(current.enabled) === enabled && Math.abs(Number(current.command_rpm ?? 0) - (enabled ? signedRpm * Number(current.override ?? 1) : 0)) < 1e-9) return { status: "skipped" };
|
||||||
|
return this.xyzacIo.command("SPINDLE_COMMAND", { spindle: 0, rpm: signedRpm, enabled });
|
||||||
|
}
|
||||||
|
|
||||||
loadXyzacSubroutines(programPath) {
|
loadXyzacSubroutines(programPath) {
|
||||||
if (!this.xyzacProgram) return { status: "verified", count: 0 };
|
if (!this.xyzacProgram) return { status: "verified", count: 0 };
|
||||||
const requirement = this.xyzacProgram.requiresSubroutines();
|
const requirement = this.xyzacProgram.requiresSubroutines();
|
||||||
@@ -323,7 +349,7 @@ export class SimSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
panelSnapshot({ task = this.task.export(), program = this.xyzacProgram ? this.xyzacProgram.snapshot() : this.program.loaded ? this.program.snapshot() : null, input = {}, tick = this.tick, motionTrace = null, includeToolpath = input.kind !== "SIM_STEP" || !this.xyzacProgram, includeConfiguration = input.kind !== "SIM_STEP" || !this.xyzacProgram, includeStableRuntime = input.kind !== "SIM_STEP" || !this.xyzacProgram } = {}) {
|
panelSnapshot({ task = this.task.export(), program = this.xyzacProgram ? this.xyzacProgram.snapshot() : this.program.loaded ? this.program.snapshot() : null, input = {}, tick = this.tick, motionTrace = null, includeToolpath = input.kind !== "SIM_STEP" || !this.xyzacProgram, includeConfiguration = input.kind !== "SIM_STEP" || !this.xyzacProgram, includeStableRuntime = input.kind !== "SIM_STEP" || !this.xyzacProgram } = {}) {
|
||||||
const motion = this.xyzacProgram ? this.wasmMotionProjection() : this.motion.export();
|
const motion = this.xyzacProgram ? this.wasmMotionProjection(program) : this.motion.export();
|
||||||
const wasmMotion = this.wasmMotion.read();
|
const wasmMotion = this.wasmMotion.read();
|
||||||
const homeLimit = this.wasmHomeLimit.read();
|
const homeLimit = this.wasmHomeLimit.read();
|
||||||
const ioState = this.xyzacIo?.canonical?.() ?? {};
|
const ioState = this.xyzacIo?.canonical?.() ?? {};
|
||||||
@@ -1681,6 +1707,8 @@ export class SimSession {
|
|||||||
}
|
}
|
||||||
const step = this.xyzacProgram.step();
|
const step = this.xyzacProgram.step();
|
||||||
if (step.status !== "verified") throw new Error(step.code);
|
if (step.status !== "verified") throw new Error(step.code);
|
||||||
|
const spindleSync = this.syncXyzacProgramSpindle(step.program);
|
||||||
|
if (spindleSync.status === "error") throw new Error(spindleSync.code ?? "wasm_program_spindle_sync_failed");
|
||||||
// Path control is produced by the WASM interpreter and
|
// Path control is produced by the WASM interpreter and
|
||||||
// applied to the WASM trajectory queue before enqueue.
|
// applied to the WASM trajectory queue before enqueue.
|
||||||
const motionControlMode = Number(step.program?.motion_control_mode ?? 0);
|
const motionControlMode = Number(step.program?.motion_control_mode ?? 0);
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ assert.equal(halfStep.payload.status, "verified");
|
|||||||
assert.equal(session.wasmMotion.controls.feed_override, 0.5);
|
assert.equal(session.wasmMotion.controls.feed_override, 0.5);
|
||||||
assert.equal(halfStep.payload.panel_snapshot.overrides.feed, 0.5);
|
assert.equal(halfStep.payload.panel_snapshot.overrides.feed, 0.5);
|
||||||
assert.ok(halfStep.payload.wasm_motion.state.velocity > 0, JSON.stringify(halfStep.payload.wasm_motion.state));
|
assert.ok(halfStep.payload.wasm_motion.state.velocity > 0, JSON.stringify(halfStep.payload.wasm_motion.state));
|
||||||
|
assert.equal(halfStep.payload.motion.velocity_mm_min, halfStep.payload.wasm_motion.state.velocity_mm_min);
|
||||||
|
assert.ok(halfStep.payload.motion.velocity_mm_min > 0);
|
||||||
|
|
||||||
const fullOverride = send("SET_OVERRIDE", { feed: 2 });
|
const fullOverride = send("SET_OVERRIDE", { feed: 2 });
|
||||||
assert.equal(fullOverride.payload.status, "verified");
|
assert.equal(fullOverride.payload.status, "verified");
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const contractSource = await readFile(new URL("../react/src/xyzac-panel-contract
|
|||||||
const sessionSource = await readFile(new URL("./sim-session.mjs", import.meta.url), "utf8");
|
const sessionSource = await readFile(new URL("./sim-session.mjs", import.meta.url), "utf8");
|
||||||
const tpSource = await readFile(new URL("./wasm-linuxcnc-tp.mjs", import.meta.url), "utf8");
|
const tpSource = await readFile(new URL("./wasm-linuxcnc-tp.mjs", import.meta.url), "utf8");
|
||||||
const combined = `${appSource}\n${panelSource}\n${contractSource}`;
|
const combined = `${appSource}\n${panelSource}\n${contractSource}`;
|
||||||
for (const symbol of ["XyzacTrtPanel", "XyzacProgramPane", "XyzacKinematicsControl", "XyzacManualPanel", "XyzacDiagnostics", "KINEMATICS_SELECT", "VISUAL_CLEAR", "POWER", "switchkinsXyzac", "Operation trace", "Alarms / recovery"]) assert.ok(combined.includes(symbol), symbol);
|
for (const symbol of ["XyzacTrtPanel", "XyzacProgramPane", "XyzacKinematicsControl", "XyzacManualPanel", "XyzacLiveParameters", "XyzacDiagnostics", "Spindle", "Feed actual", "TP velocity", "Live parameters", "KINEMATICS_SELECT", "VISUAL_CLEAR", "POWER", "switchkinsXyzac", "Operation trace", "Alarms / recovery"]) assert.ok(combined.includes(symbol), symbol);
|
||||||
for (const symbol of ["wasmSnapshotReady", "manualReady", "sourceJogScale", "sourceVelocityScale", "spindleOverrideSource", "SET_JOG_SCALE", "SET_MAX_VELOCITY", "SET_SPINDLE_OVERRIDE", "WASM PENDING", "LinuxCNC WASM override-limits command unavailable"]) assert.ok(appSource.includes(symbol), symbol);
|
for (const symbol of ["wasmSnapshotReady", "manualReady", "sourceJogScale", "sourceVelocityScale", "spindleOverrideSource", "SET_JOG_SCALE", "SET_MAX_VELOCITY", "SET_SPINDLE_OVERRIDE", "WASM PENDING", "LinuxCNC WASM override-limits command unavailable"]) assert.ok(appSource.includes(symbol), symbol);
|
||||||
for (const forbidden of [/defaultValue=\"50\"/, /defaultValue=\"100\"/, /setBlockDelete/, /setOptionalStop/, /state\.status === \"running\" \? \"READING\"/]) assert.equal(forbidden.test(appSource), false, String(forbidden));
|
for (const forbidden of [/defaultValue=\"50\"/, /defaultValue=\"100\"/, /setBlockDelete/, /setOptionalStop/, /state\.status === \"running\" \? \"READING\"/]) assert.equal(forbidden.test(appSource), false, String(forbidden));
|
||||||
for (const forbidden of [/_sim_/, /_malloc/, /\bHEAP(?:U|F|I)?\d*\b/, /navigator\.storage/, /\bOPFS\b/i, /postMessage\s*\(/]) assert.equal(forbidden.test(combined), false, String(forbidden));
|
for (const forbidden of [/_sim_/, /_malloc/, /\bHEAP(?:U|F|I)?\d*\b/, /navigator\.storage/, /\bOPFS\b/i, /postMessage\s*\(/]) assert.equal(forbidden.test(combined), false, String(forbidden));
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function toPose(values) { return Object.fromEntries(["x", "y", "z", "a", "b", "c
|
|||||||
export class WasmLinuxcncTp {
|
export class WasmLinuxcncTp {
|
||||||
constructor(core, { cycleTime = 0.001, velocityLimit = 35, accelerationLimit = 300, limits = {} } = {}) {
|
constructor(core, { cycleTime = 0.001, velocityLimit = 35, accelerationLimit = 300, limits = {} } = {}) {
|
||||||
this.core = core;
|
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_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.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;
|
this.cycleTime = cycleTime;
|
||||||
this.velocityLimit = velocityLimit;
|
this.velocityLimit = velocityLimit;
|
||||||
this.accelerationLimit = accelerationLimit;
|
this.accelerationLimit = accelerationLimit;
|
||||||
@@ -192,7 +192,8 @@ export class WasmLinuxcncTp {
|
|||||||
this.controls.feed_override = feedOverride;
|
this.controls.feed_override = feedOverride;
|
||||||
const position = toPose(doubles);
|
const position = toPose(doubles);
|
||||||
const joints = this.commandedJoints(position, this.activeTag);
|
const joints = this.commandedJoints(position, this.activeTag);
|
||||||
const state = { position, velocity: doubles[9], acceleration: this.accelerationLimit, progress: doubles[10], tick: ints[0], queue_depth: this.queueDepthOverride === null ? ints[1] : this.queueDepthOverride, active_queue: activeId ? 1 : 0, active_id: activeId, active_tag: this.activeTag, joint_commanded: joints, joint_actual: joints.slice(), motion_type: this.core.HEAP32[this.statePointer / 4 + 25], status: this.core.HEAP32[this.statePointer / 4 + 26], exec_state: this.executionState, velocity_limit: velocityLimit, velocity_scale: velocityScale, jog_velocity_linear: this.jogVelocity("x"), jog_velocity_angular: this.jogVelocity("a"), feed_override: feedOverride, jog_scale: jogScale, source: "linuxcnc-source-tp" };
|
const velocityMmMin = Number(call(this.core, "sim_linuxcnc_tp_velocity_mm_min", "number", ["number"], [this.planner]));
|
||||||
|
const state = { position, velocity: doubles[9], velocity_mm_min: velocityMmMin, acceleration: this.accelerationLimit, progress: doubles[10], tick: ints[0], queue_depth: this.queueDepthOverride === null ? ints[1] : this.queueDepthOverride, active_queue: activeId ? 1 : 0, active_id: activeId, active_tag: this.activeTag, joint_commanded: joints, joint_actual: joints.slice(), motion_type: this.core.HEAP32[this.statePointer / 4 + 25], status: this.core.HEAP32[this.statePointer / 4 + 26], exec_state: this.executionState, velocity_limit: velocityLimit, velocity_scale: velocityScale, jog_velocity_linear: this.jogVelocity("x"), jog_velocity_angular: this.jogVelocity("a"), feed_override: feedOverride, jog_scale: jogScale, source: "linuxcnc-source-tp" };
|
||||||
this.lastState = state;
|
this.lastState = state;
|
||||||
return { status: "verified", state };
|
return { status: "verified", state };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user