docs: add axis run process working set
@@ -3226,3 +3226,181 @@ Pause 修复已提交并推送到云仓库 `master`,提交号为 `58854f9`。
|
||||
### 结论
|
||||
|
||||
业务提交 `4ba45b2 feat: complete working8 pause semantics` 已成功推送到云仓库 `origin/master`。本次日志已按项目规则追加,随后将单独提交并推送日志提交,确保远端也包含本次执行记录。
|
||||
|
||||
## 2026-07-07 10:38 EDT
|
||||
|
||||
### 提问
|
||||
|
||||
用户要求通过 HTTPS 方式发布网站到服务器 `82.156.24.101`,SSH 用户为 `ubuntu`,密码已在日志中脱敏,说明 `8080-8099` 端口已经开放,要求使用 `8092` 端口,域名为 `codexcnc.com`。
|
||||
|
||||
### 执行过程
|
||||
|
||||
1. 读取项目根目录结构,确认当前仓库路径为 `/home/mes123456/cnc_wams`,项目中存在多个 Web 目录。
|
||||
2. 查看 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/package.json`,确认站点名称为 `web-rtcp-5axis-xyzbc-trt-sim`,构建命令为 `npm run build`,构建脚本为 `node scripts/build-static.mjs`。
|
||||
3. 查看 `web-rtcp-5axis-sim-plan/app/package.json` 和两个项目 README,对比后确认本次应发布的站点是 `web-rtcp-5axis-xyzbc-trt-sim-plan/app`,其静态产物目录为 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist`。
|
||||
4. 查看 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/scripts/build-static.mjs`,确认构建会复制 `index.html`、`src`、LinuxCNC 配置、参考资源和 WASM 运行时资源到 `dist`。
|
||||
5. 查看 `gptlog-process/gpdlog.md` 中今天早前的发布记录,确认该站点之前已经配置过 `codexcnc.com`、Nginx、Let's Encrypt 证书和 `8092` HTTPS 服务;本次仍重新验证并重新发布最新构建产物。
|
||||
6. 执行 `getent ahostsv4 codexcnc.com`,确认域名 `codexcnc.com` 解析到 `82.156.24.101`。
|
||||
7. 执行 `curl -k -I https://codexcnc.com:8092/`,确认 `8092` 端口当前已返回 `HTTP/2 200`。
|
||||
8. 执行 `curl -I https://codexcnc.com/`,发现标准 `443` 端口公网访问在响应阶段被重置;因此后续以用户指定的 `8092` 端口作为发布目标,并额外检查 443 原因。
|
||||
9. 确认本机存在 `/usr/bin/sshpass`,随后使用用户提供的 SSH 信息登录远端服务器;日志中不记录明文密码。
|
||||
10. 在远端执行系统检查,确认服务器主机名为 `VM-0-3-ubuntu`,系统为 Ubuntu 24.04 LTS,Nginx 版本为 `nginx/1.24.0 (Ubuntu)`,Nginx 状态为 active。
|
||||
11. 在远端执行 `sudo -n true`,确认当前用户可无交互执行 sudo。
|
||||
12. 在远端执行 `ss -ltnp` 检查监听端口,确认 `80`、`443`、`8092` 均由 Nginx 监听。
|
||||
13. 执行 `openssl s_client -connect codexcnc.com:8092 -servername codexcnc.com`,确认 `8092` 端口证书主体为 `CN=codexcnc.com`,签发方为 Let's Encrypt `YE1`,有效期为 `Jul 7 11:53:17 2026 GMT` 至 `Oct 5 11:53:16 2026 GMT`。
|
||||
14. 执行 `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run build`,构建成功,输出 `gmoccapy_static_build=ok`。
|
||||
15. 检查构建产物大小和文件数,确认 `dist` 目录大小约 `21M`,共 `826` 个文件。
|
||||
16. 在远端检查 `/var/www/codexcnc-8092`、`/etc/nginx/sites-available`、`/etc/nginx/sites-enabled`,确认站点目录和 Nginx 配置目录存在。
|
||||
17. 在远端执行 `sudo nginx -T` 过滤检查,确认 `codexcnc.com` 配置中包含 `listen 8092 ssl http2` 和 `root /var/www/codexcnc-8092`。
|
||||
18. 在本地将 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist` 打包为临时压缩包 `/tmp/codexcnc-dist-20260707103349.tgz`。
|
||||
19. 使用 `scp` 将临时压缩包上传到远端 `/tmp/codexcnc-dist.tgz`。
|
||||
20. 在远端创建 `/var/www/codexcnc-8092.new`,解压最新静态文件到该目录。
|
||||
21. 在远端设置新目录属主为 `www-data:www-data`,目录权限为 `755`,文件权限为 `644`。
|
||||
22. 在远端备份旧站点目录到 `/var/backups/codexcnc-8092/site-时间戳`,并将旧目录移动到 `/var/www/codexcnc-8092.previous`。
|
||||
23. 将新目录移动为正式站点目录 `/var/www/codexcnc-8092`。
|
||||
24. 执行 `sudo nginx -t`,确认 Nginx 配置语法正确。
|
||||
25. 执行 `sudo systemctl reload nginx`,完成 Nginx 热重载。
|
||||
26. 删除远端临时压缩包 `/tmp/codexcnc-dist.tgz`。
|
||||
27. 验证远端正式站点目录大小约 `21M`,文件数为 `826`。
|
||||
28. 再次执行 `curl -I https://codexcnc.com:8092/`,确认首页返回 `HTTP/2 200`,`last-modified` 已更新为本次发布时间。
|
||||
29. 执行 `curl -I https://codexcnc.com:8092/src/main.js`,确认主脚本返回 `HTTP/2 200`,`content-type` 为 `text/javascript`。
|
||||
30. 执行 `curl -vkI https://codexcnc.com/` 和 `curl --http1.1 -I https://codexcnc.com/`,确认公网访问标准 `443` 端口仍在响应阶段被重置。
|
||||
31. 在远端本机执行 `curl -k --http1.1 -I https://127.0.0.1/`、`curl -k --http1.1 -I https://127.0.0.1:8092/` 和 `curl -k -I --resolve codexcnc.com:443:127.0.0.1 https://codexcnc.com/`,确认 Nginx 本机 443 和 8092 都能返回 `200`;因此公网 443 问题更像外部链路或安全策略问题,不是站点文件问题。
|
||||
32. 检查远端 `ss` 和 `lsof`,确认 `443` 确实由 Nginx 监听。
|
||||
33. 为匹配用户指定的 `8092` 发布目标,备份 `/etc/nginx/sites-available/codexcnc-8092` 到 `/var/backups/codexcnc-8092/codexcnc-8092.nginx.时间戳.before-http-redirect.conf`。
|
||||
34. 使用 `sed` 将远端 HTTP `80` 端口跳转从 `https://$host$request_uri` 调整为 `https://$host:8092$request_uri`。
|
||||
35. 再次执行 `sudo nginx -t` 和 `sudo systemctl reload nginx`,确认配置正确并生效。
|
||||
36. 执行 `curl -I http://codexcnc.com/`,确认 HTTP 入口返回 `301 Moved Permanently`,跳转目标为 `https://codexcnc.com:8092/`。
|
||||
37. 执行 `curl -I https://codexcnc.com:8092/wasm-port/build/wasm/core/linuxcnc_interp.wasm`,确认 WASM 资源返回 `HTTP/2 200`,`content-type` 为 `application/wasm`。
|
||||
38. 再次检查 `8092` 证书,确认仍为 Let's Encrypt 签发给 `codexcnc.com` 的有效证书。
|
||||
39. 使用 Playwright 打开 `https://codexcnc.com:8092/`,确认响应状态为 `200`,页面标题为 `Web XYZBC TRT RTCP Simulation`,当前 URL 为 `https://codexcnc.com:8092/`。
|
||||
40. Playwright 检查 `#app` 文本长度为 `2144`,页面按钮数量为 `73`,未捕获 console error 或 pageerror。
|
||||
41. 执行 `git status --short`,确认追加日志前工作区干净。
|
||||
42. 使用 `apply_patch` 将本次 HTTPS 发布、验证和配置调整过程追加到 `gptlog-process/gpdlog.md`。
|
||||
|
||||
### 结论
|
||||
|
||||
已完成网站最新版本的 HTTPS 发布,正式访问地址为 `https://codexcnc.com:8092/`。该地址使用 Let's Encrypt 为 `codexcnc.com` 签发的有效证书,首页、主脚本和 WASM 资源均返回 `200`,Playwright 浏览器加载验证通过且无前端错误。HTTP 地址 `http://codexcnc.com/` 已调整为自动跳转到 `https://codexcnc.com:8092/`。标准 `https://codexcnc.com/` 的 443 公网访问仍出现响应阶段连接重置,但远端本机 443 服务正常;本次按用户指定的 8092 端口完成发布。
|
||||
## 2026-07-07 11:07 EDT
|
||||
|
||||
### 提问
|
||||
用户要求参考 `/home/mes123456/cnc_wams/work/working8-暂停按钮` 中的文档,严格深入解决 `/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan` 暂停按钮没有引用 WASM 新暂停程序、按钮不能真正暂停的问题。
|
||||
|
||||
### 执行过程
|
||||
1. 读取 `work/working8-暂停按钮` 的 README、开发步骤、验收证据,确认目标语义是 LinuxCNC task/interpreter 与 motion 双层暂停:`EMC_TASK_PLAN_PAUSE/RESUME`、`motion.paused`、`interpResumeState`、`taskPaused`、`motionPaused` 必须由 WASM 状态驱动。
|
||||
2. 搜索并检查目标项目的 `app/src/state/store.js`、`app/src/runtime/linuxcnc-task-hal-runtime.js`、`app/src/state/linuxcnc-task-policy.js`、`app/src/ui/axis-shell.js`,确认当前源码已经能加载 task-hal WASM,也有 `motionPaused` 映射,但暂停路径存在前端 `taskHalPauseLock` 强制伪造 paused、停止 status loop 后只读一次 WASM 状态的风险。
|
||||
3. 校验 `wasm-port` 底层:`verify_task_hal_wasm.mjs` 已覆盖 `pause_freezes_motion_queue`、`resume_restores_interp_resume_state`、`step_returns_to_paused`,并确认 `app/dist/wasm-port/build/wasm/task-hal/linuxcnc_task_hal.wasm` 与 `wasm-port/build/wasm/task-hal/linuxcnc_task_hal.wasm` 哈希一致。
|
||||
4. 修改 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/src/state/store.js`:暂停命令不再用 `preserveMachine` 伪造成功状态,必须等待 WASM 返回 `motionPaused/taskPaused/interpState=paused` 后才进入 paused;Resume 同样不再用前端状态覆盖 WASM 返回状态。
|
||||
5. 修改 `applyTaskHalStatusPatch`:状态判定以 WASM status 的 `ui.motionPaused`、`motionStatus.motion.paused`、`task.taskPaused`、`interpState` 为准;去掉 pauseLock 对 paused 状态本身的伪造,只保留显示层锁定。
|
||||
6. 修改暂停后的 status loop:暂停期间保留低频/单步状态监控,`shouldContinueTaskHalStatusLoop` 在真实 paused 状态下继续监控,使 Web 侧持续观察 WASM 是否仍处于暂停,而不是停掉轮询造成假冻结。
|
||||
7. 修复浏览器 trace 暂停后位置跳动问题:暂停确认后保留 `taskHalPauseLock` 仅用于显示坐标、sample、motionIndex、tcpPose、rtcpFrame、dro 的冻结;同时在暂停态阻止异步 kinematics refresh 覆盖显示位置。该锁不再作为 paused 状态来源,paused 状态仍必须来自 WASM。
|
||||
8. 运行调试脚本分析失败 trace,确认旧问题表现为 `axisPose/dro` 或 `runtimeTcp/uiExecution.tcp/canvasToolhead` 在 pause hold 阶段变化;补充 tcpPose/rtcpFrame/dro 锁定后重新验证通过。
|
||||
9. 重新构建 `app/dist`,确保静态页面引用更新后的 `app/src/state/store.js` 与当前 WASM artifact。
|
||||
|
||||
### 验证结果
|
||||
- `node wasm-port/tests/wasm/node/verify_task_hal_wasm.mjs` 通过,输出包含:`pause_freezes_motion_queue=ok`、`resume_restores_interp_resume_state=ok`、`step_returns_to_paused=ok`。
|
||||
- `node tests/node/verify_run_feedback_loop.mjs` 通过,输出包含:`pause_uses_motion_paused_gate=ok`、`pause_freezes_task_hal_status_loop=ok`。
|
||||
- `npm --prefix app run build` 通过,输出:`gmoccapy_static_build=ok`。
|
||||
- `APP_URL_PATH=/web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/index.html node tools/trace-pause-position-json.mjs` 最终通过,输出:`pause_position_status=passed-position-frozen`、`position_changed=false`、`changed_fields=`,trace 路径:`/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/pause-position-traces/pause-position-20260707T150717Z/trace.json`。
|
||||
- `RUN_STABLE_BEFORE_FIRST_PAUSE_MS=3000 node tools/verify-estop-power-home-run-pause-50ms.mjs` 在新构建后通过,输出:`verification_status=passed`,截图目录:`/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/screenshots/estop-power-home-run-pause-50ms-20260707T145810Z`,manifest:`/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/screenshots/estop-power-home-run-pause-50ms-20260707T145810Z/manifest.json`。
|
||||
- `npm --prefix app run smoke:node` 通过,输出:`xyzbc_trt_web_app_smoke=ok`。
|
||||
|
||||
### 结论
|
||||
已严格修复暂停按钮的真实 WASM 接入问题:暂停/恢复现在由 task-hal WASM 状态确认,Web 不再仅靠前端锁伪造暂停;暂停期间继续监控 WASM paused 状态,同时冻结 UI/Three.js 显示坐标、TCP、DRO、sample,最终浏览器 trace 和 50ms 长流程均证明暂停后位置不再变化。
|
||||
|
||||
## 2026-07-07 16:31 EDT
|
||||
|
||||
### 提问
|
||||
用户要求分析 `/home/mes123456/cnc_wams/linuxcnc` 源程序中 AXIS 主界面“急停、上电、Home、执行、暂停、单步执行”的详细调用链条和状态机制,包括 C++ task、motion、状态记录方式、按钮先决条件;并基于分析结果指导完善 `/home/mes123456/cnc_wams/wasm-port` 的 LinuxCNC WASM 和 `/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan` 程序,最后将分析结果写入单独文档。
|
||||
|
||||
### 执行过程
|
||||
1. 读取根目录 `AGENTS.md`,确认本次执行结束后必须将完整过程日志以中文追加到 `gptlog-process/gpdlog.md`,日志时间戳精确到分钟。
|
||||
2. 读取 `wasm-port/AGENTS.md`,确认 `wasm-port` 的规则是:LinuxCNC 源码为语义源,移植代码应优先复用 vendored LinuxCNC,不应扩展项目自写 G-code、planner、task、motion 语义。
|
||||
3. 使用 `ls`、`find`、`rg --files` 梳理仓库结构,确认主要分析对象包括 `linuxcnc/bin/axis`、`linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc`、`linuxcnc/src/emc/task/emctaskmain.cc`、`linuxcnc/src/emc/task/emctask.cc`、`linuxcnc/src/emc/task/taskintf.cc`、`linuxcnc/src/emc/motion/command.c`、`linuxcnc/src/emc/motion/homing.c`、`linuxcnc/src/emc/nml_intf/emc_nml.hh`。
|
||||
4. 在 `linuxcnc/bin/axis` 中定位 AXIS 按钮入口:`estop_clicked()`、`onoff_clicked()`、`task_run()`、`task_pause()`、`task_step()`、`task_resume()`、`task_pauseresume()`、`home_all_joints()`、`home_joint()`,确认 AXIS UI 侧通过 `s.poll()` 读取状态,通过 `c.state()`、`c.mode()`、`c.home()`、`c.auto()` 发送命令。
|
||||
5. 在 `emcmodule.cc` 中定位 Python C 扩展映射:`mode()` 创建 `EMC_TASK_SET_MODE`,`state()` 创建 `EMC_TASK_SET_STATE`,`home()` 创建 `EMC_JOINT_HOME`,`emcauto()` 创建 `EMC_TASK_PLAN_RUN/PAUSE/RESUME/STEP`,确认 AXIS 按钮不是直接操作 motion,而是写 NML 命令。
|
||||
6. 在 `emctaskmain.cc` 中分析 `emcTaskPlan()` 的状态门禁,确认 task 根据 `task.state`、`task.mode`、`task.interpState` 分层允许或拒绝命令;重点检查 OFF/ESTOP/ESTOP_RESET、ON/MANUAL、ON/AUTO/IDLE、ON/AUTO/READING、ON/AUTO/PAUSED、ON/AUTO/WAITING、ON/MDI 的命令接收分支。
|
||||
7. 在 `emctaskmain.cc` 中分析 `EMC_TASK_PLAN_RUN`、`EMC_TASK_PLAN_PAUSE`、`EMC_TASK_PLAN_RESUME`、`EMC_TASK_PLAN_STEP` 的处理:Run 设置 `interpState=READING` 和清 pause/step;Pause 调 `emcTrajPause()` 并设置 `interpState=PAUSED/task_paused=1`;Resume 调 `emcTrajResume()` 并恢复 `interpResumeState`;Step 在 IDLE/READING/PAUSED/WAITING 下有不同分支。
|
||||
8. 在 `emctask.cc` 中分析 `emcTaskSetState()`:`OFF` 分支 abort、disable、IO abort、清 volatile home;`ON` 分支调用 `emcTrajEnable()`;`ESTOP_RESET` 分支释放 IO estop 并 abort/synch;`ESTOP` 分支 motion abort、IO estop on、traj disable、task abort、清 volatile home。
|
||||
9. 在 `emctask.cc` 中分析 `determineState()` 和 `emcTaskUpdate()`,确认 GUI 可见 `task.state` 不是简单按钮写入,而是由 IO estop 和 `motion.traj.enabled` 推导:IO estop 为真即 `ESTOP`,非 estop 且 motion disabled 为 `ESTOP_RESET`,motion enabled 为 `ON`。
|
||||
10. 在 `taskintf.cc` 中分析 task 到 motion 的桥接:`emcTrajEnable()` 下发 `EMCMOT_ENABLE`,`emcTrajDisable()` 下发 `EMCMOT_DISABLE`,`emcTrajPause()` 下发 `EMCMOT_PAUSE`,`emcTrajResume()` 下发 `EMCMOT_RESUME`,`emcTrajStep()` 下发 `EMCMOT_STEP`,`emcJointHome()` 下发 `EMCMOT_JOINT_HOME`。
|
||||
11. 在 `motion/command.c` 中分析 motion 侧命令:`EMCMOT_ENABLE` 检查 HAL enable 输入并设置 enabling,`EMCMOT_DISABLE` 清 enabling,`EMCMOT_PAUSE` 调 `tpPause()` 并设置 `emcmotStatus->paused=1`,`EMCMOT_RESUME` 清 stepping 并恢复 planner,`EMCMOT_STEP` 设置 `idForStep/stepping`,`EMCMOT_JOINT_HOME` 要求 FREE 模式、未 inhibit、未 homing、motion enabled。
|
||||
12. 在 `motion/homing.c` 中分析 homing 状态机,确认 `H[joint].homing`、`H[joint].homed`、`H[joint].home_state`、`joint_in_sequence` 通过 HAL pin 输出;完成时 `homed=1/home_state=HOME_IDLE`,异常时清所有 homed 和 free_tp。
|
||||
13. 在 `taskintf.cc` 中分析 `emcMotionUpdate()`、`emcTrajUpdate()` 状态发布,确认 motion 共享状态被复制到 `emcStatus->motion`,包括 enabled、paused、queue、id、current_vel 等;在 `emcmodule.cc` 中确认 Python `linuxcnc.stat()` 暴露 `task_state/task_mode/interp_state/task_paused/enabled/paused/single_stepping` 等字段。
|
||||
14. 检查 `wasm-port` 的 README、`docs/source-reuse-map.md`、`docs/axis-style-simulation-implementation.md` 和当前 `runtime/core/linuxcnc_wrap/linuxcnc_task_hal_wasm.cpp`、`runtime/sdk/src/linuxcnc-task-hal.js`,确认当前 task/HAL WASM 仍是 `phase4_minimal`,有手写 JSON 解析、手写 task 状态和从 G-code 行派生 motion 的过渡实现。
|
||||
15. 检查 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/src/main.js`、`app/src/state/linuxcnc-task-policy.js`、`app/src/runtime/linuxcnc-task-hal-runtime.js`、既有 working 文档,确认 Web 项目已有 AXIS 按钮策略、task/HAL runtime、浏览器和 Node 验证基础,但后续仍应继续把 Web store 状态收敛为 taskHalStatus 的投影。
|
||||
16. 使用 `apply_patch` 新增独立文档 `项目分析/AXIS主控制按钮调用链与WASM完善指南.md`,用中文写入 929 行分析内容,覆盖总体架构、AXIS UI 入口、Python C 扩展到 NML、task 调度、急停、上电、Home、Run、Pause、Step、Resume、按钮前置条件、wasm-port 完善路线、Web 项目完善路线和验证矩阵。
|
||||
17. 使用 `wc -l` 和 `sed` 抽查新文档,确认文件已生成且开头内容正确。
|
||||
18. 使用 `git status --short` 检查工作区,确认本次新增了 `项目分析/AXIS主控制按钮调用链与WASM完善指南.md`,同时工作区存在多处用户或既有改动,本次未回退这些无关改动。
|
||||
19. 使用 `date '+%Y-%m-%d %H:%M %Z'` 获取日志时间戳为 `2026-07-07 16:31 EDT`。
|
||||
20. 使用 `apply_patch` 将本次完整执行过程追加到 `gptlog-process/gpdlog.md`。
|
||||
|
||||
### 结论
|
||||
已完成 LinuxCNC AXIS 主控制按钮的源码级调用链和状态机制分析,并写入独立文档 `项目分析/AXIS主控制按钮调用链与WASM完善指南.md`。文档明确指出 LinuxCNC 的真实链路是 AXIS Python/Tk 按钮通过 Python C 扩展发送 `EMC_TASK_*`/`EMC_JOINT_*` NML 命令,由 C++ task 层门禁和状态合成,再通过 `taskintf.cc` 下发到 C motion 层处理,最终由 `emcStatus` 暴露给 GUI。文档同时给出 `wasm-port` 和 `web-rtcp-5axis-xyzbc-trt-sim-plan` 的完善路线:减少手写 CNC 语义,把按钮和 Web 状态收敛到 LinuxCNC task/motion 状态机与 WASM status 投影。
|
||||
|
||||
## 2026-07-07 16:39
|
||||
|
||||
### 提问
|
||||
用户要求分析 `/home/mes123456/cnc_wams/linuxcnc` 源程序中 AXIS 主界面的“急停、上电、Home、执行、暂停、单步执行”的详细调用链条和状态机制,包括 C++ task、motion、状态记录方式、每个按钮的先决条件;并指导完善 `/home/mes123456/cnc_wams/wasm-port` 的 LinuxCNC WASM 和 `/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan` 程序,分析结果写入单独文档。
|
||||
|
||||
### 执行过程
|
||||
1. 读取并遵守根目录 `AGENTS.md`,确认每次 GPT/Codex 执行完毕后必须将完整执行过程以中文追加到 `gptlog-process/gpdlog.md`,时间戳精确到分钟。
|
||||
2. 读取 `wasm-port/AGENTS.md`,确认 `wasm-port` 的约束是:上游 `linuxcnc/` 只作为只读语义源,移植代码应放在 `wasm-port/`,应优先复用 LinuxCNC 源码,不应扩展项目自写 CNC 语义。
|
||||
3. 使用 `rg --files`、`find`、`ls` 梳理仓库结构,确认 AXIS UI 文件、task/motion 源码、`wasm-port` 与 `web-rtcp-5axis-xyzbc-trt-sim-plan` 的相关目录。
|
||||
4. 在 `linuxcnc/share/axis/tcl/axis.tcl` 中定位主菜单和工具栏按钮:急停 `estop_clicked`、电源 `onoff_clicked`、运行 `task_run`、单步 `task_step`、暂停 `task_pause`、恢复 `task_resume`、工具栏暂停/恢复合并 `task_pauseresume`、Home `home_joint`。
|
||||
5. 分析 `axis.tcl update_state()`,确认按钮 UI 门禁:电源按钮要求 `task_state != STATE_ESTOP`;Run 要求 `STATE_ON && INTERP_IDLE`;Home/Unhome/Zero 要求 `STATE_ON && INTERP_IDLE`;Step 要求 `STATE_ON && taskfile != ""`;Pause 菜单要求 `STATE_ON && (INTERP_READING || INTERP_WAITING)`;Resume 菜单要求 `STATE_ON && INTERP_PAUSED`;工具栏 Pause/Resume 要求 `STATE_ON && interp_state != IDLE`。
|
||||
6. 在 `linuxcnc/src/emc/usr_intf/axis/scripts/axis.py` 中定位按钮回调,确认急停通过 `s.poll()` 后按 `STATE_ESTOP` 切换 `STATE_ESTOP_RESET/STATE_ESTOP`;电源按钮仅在当前 `STATE_ESTOP_RESET` 时发送 `STATE_ON`,否则发送 `STATE_OFF`;Home 通过 `manual_ok()`、`ensure_mode(MODE_MANUAL)`、`go_home()`、`c.home()`;Run 通过 `ensure_mode(MODE_AUTO)`、`c.auto(AUTO_RUN, line)`;Pause/Resume/Step 通过 `c.auto(AUTO_PAUSE/AUTO_RESUME/AUTO_STEP)`。
|
||||
7. 在 `axis.py AxisCanon.update()` 中分析状态回写,确认 AXIS 通过 `linuxcnc.stat().poll()` 读取 `task_mode/task_state/task_paused/taskfile/interp_state/paused/motion_mode/homed` 等字段,然后写入 Tk 变量,触发 Tcl trace 刷新 UI。
|
||||
8. 在 `linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc` 中分析 Python C++ 扩展,确认 `state()` 创建 `EMC_TASK_SET_STATE`,`mode()` 创建 `EMC_TASK_SET_MODE`,`home()` 创建 `EMC_JOINT_HOME`,`emcauto()` 创建 `EMC_TASK_PLAN_RUN/PAUSE/RESUME/STEP`,`stat` 对象暴露 `task_state/task_mode/interp_state/task_paused/paused/homed`。
|
||||
9. 在 `linuxcnc/src/emc/nml_intf/emc.hh` 中确认核心枚举:`EMC_TASK_STATE` 包含 `ESTOP/ESTOP_RESET/OFF/ON`,`EMC_TASK_MODE` 包含 `MANUAL/AUTO/MDI`,`EMC_TASK_INTERP` 包含 `IDLE/READING/PAUSED/WAITING`,`EMC_TASK_EXEC` 包含 `DONE/WAITING_FOR_MOTION/...`。
|
||||
10. 在 `linuxcnc/src/emc/task/emctaskmain.cc` 中分析 task 主循环和文件头说明,确认 LinuxCNC task 每周期调用 `emcTaskPlan()` 与 `emcTaskExecute()`;`emcTaskPlan()` 按 task state、task mode、interp state 对新命令做门禁;`emcTaskExecute()` 从 `interp_list` 取命令、检查 pre/post condition、下发命令并更新 `execState`。
|
||||
11. 在 `emctaskmain.cc` 中分析 `EMC_TASK_PLAN_RUN`、`EMC_TASK_PLAN_PAUSE`、`EMC_TASK_PLAN_RESUME`、`EMC_TASK_PLAN_STEP` 分支,确认 Run 设置 `interpState=READING/task_paused=0`,Pause 保存 `interpResumeState` 并设置 `interpState=PAUSED/task_paused=1`,Resume 恢复 `interpResumeState` 并清 `task_paused/single_stepping/stepping`,Step 在 IDLE/READING/PAUSED/WAITING 下分别启动运行、设置单步或触发 motion step。
|
||||
12. 在 `linuxcnc/src/emc/task/emctask.cc` 中分析 `emcTaskAbort()`、`emcTaskSetMode()`、`emcTaskSetState()`、`determineMode()`、`determineState()`、`emcTaskUpdate()`,确认 task 可见状态由下级状态合成:`io.aux.estop` 为真即 `ESTOP`,非急停但 motion disabled 为 `ESTOP_RESET`,motion enabled 为 `ON`;task mode 由 motion traj mode 和 `mdiOrAuto` 推导。
|
||||
13. 在 `linuxcnc/src/emc/task/taskintf.cc` 中分析 task 到 motion 的桥接,确认 `emcJointHome()` 写 `EMCMOT_JOINT_HOME`,`emcTrajEnable()` 写 `EMCMOT_ENABLE`,`emcTrajDisable()` 写 `EMCMOT_DISABLE`,`emcTrajPause()` 写 `EMCMOT_PAUSE`,`emcTrajResume()` 写 `EMCMOT_RESUME`,`emcTrajStep()` 写 `EMCMOT_STEP`。
|
||||
14. 在 `linuxcnc/src/emc/motion/command.c` 中分析 motion 命令处理,确认 `EMCMOT_PAUSE` 调 `tpPause()` 并设置 `emcmotStatus->paused=1`,`EMCMOT_RESUME` 清 stepping 并 `tpResume()`,`EMCMOT_STEP` 在 paused 状态下设置 `idForStep/stepping` 并临时恢复,`EMCMOT_JOINT_HOME` 要求 FREE 模式、无 homing inhibit、无其他 homing、motion enable。
|
||||
15. 在 `linuxcnc/src/emc/motion/control.c` 中分析单步 motion 侧完成条件,确认当 `emcmotStatus->stepping` 且 motion id 改变时,motion 自动 `tpPause()`、清 stepping、保持 paused。
|
||||
16. 在 `linuxcnc/src/emc/motion/homing.c` 和 `homing.h` 中分析 Home 状态机,确认 `do_home_joint()` 触发单 joint 或 home all,`do_homing()` 每个 servo period 推进状态,完成后 `homed=1/home_state=HOME_IDLE`,`get_allhomed()` 遍历所有 active joint 的 homed 状态。
|
||||
17. 检查 `wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_task_hal_wasm.cpp`、`linuxcnc_motion_runtime.c`、`wasm-port/runtime/sdk/src/linuxcnc-task-hal.js`,确认当前 WASM task/HAL runtime 仍是 `phase4_minimal`:使用字符串和 JSON 手写状态,`SET_STATE` 直接写 state,Home 简化为零位运动,Step 只有部分 `single_stepping`,尚未完整移植 LinuxCNC 的 `determineState()`、`homing.c` 和 task/motion 双层单步语义。
|
||||
18. 检查 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/src/state/linuxcnc-task-policy.js`、`app/src/state/store.js`、`app/src/runtime/linuxcnc-task-hal-runtime.js` 和既有文档,确认 Web 端已有按钮策略和 task/HAL runtime 接入,但仍应继续把 `taskState/interpState/allHomed/taskPaused/motionPaused` 收敛为 WASM status 投影,减少 reducer 乐观赋值。
|
||||
19. 使用 `apply_patch` 新增独立中文文档 `项目分析/AXIS主界面按钮调用链与WASM完善建议.md`,共 723 行,覆盖总体调用链、状态模型、UI 门禁、急停、上电、Home、Run、Pause/Resume、Step、每个按钮先决条件与状态影响总表、`wasm-port` 完善建议、`web-rtcp` 完善建议和推荐验收序列。
|
||||
20. 使用 `wc -l` 与 `sed` 抽查新文档,确认文件已生成且开头内容正确。
|
||||
21. 使用 `git status --short` 检查工作区,确认本次新增 `项目分析/AXIS主界面按钮调用链与WASM完善建议.md`;同时工作区存在多处用户或既有改动,本次未回退、未覆盖这些无关改动。
|
||||
22. 使用 `date '+%Y-%m-%d %H:%M'` 获取日志时间戳为 `2026-07-07 16:39`。
|
||||
23. 使用 `apply_patch` 将本次完整执行过程追加到 `gptlog-process/gpdlog.md`。
|
||||
|
||||
### 结论
|
||||
已完成 LinuxCNC AXIS 主界面“急停、上电、Home、执行、暂停、单步执行”的源码级调用链、状态机制、按钮先决条件和状态记录方式分析,并写入独立文档 `项目分析/AXIS主界面按钮调用链与WASM完善建议.md`。文档明确指出真实机制是 AXIS Tcl/Python 发 NML 命令,C++ task 层按状态/模式/解释器状态门禁并合成 status,C motion/homing 层维护运动、暂停、单步和回零状态,GUI 通过 `linuxcnc.stat()` 轮询 status 后刷新按钮。文档同时给出 `wasm-port` 与 `web-rtcp-5axis-xyzbc-trt-sim-plan` 的后续完善路线:把 task state、Home、Pause/Resume/Step 从 JS 或最小 wrapper 逐步下沉到 LinuxCNC-derived WASM runtime,并让 Web store 只作为 status 投影。
|
||||
|
||||
## 2026-07-07 16:43 EDT
|
||||
|
||||
### 提问
|
||||
用户要求按 `/home/mes123456/cnc_wams/项目分析/AXIS主控制按钮调用链与WASM完善指南.md` 文档,在 `/home/mes123456/cnc_wams/work/working9-执行过程按钮` 创建如下文档:`01-项目功能内容`、`02-项目程序开发详细步骤`、`03-推进台账`、`04-任务矩阵`、`05-验收证据`、`06-决策记录`,并另加 README 索引,便于后续按任务矩阵继续推进。
|
||||
|
||||
### 执行过程
|
||||
1. 读取根目录 `AGENTS.md` 规则,确认本次执行结束后必须将完整执行过程以中文追加到 `gptlog-process/gpdlog.md`,时间戳精确到分钟。
|
||||
2. 使用 `rg --files`、`find`、`sed` 检查仓库中与本任务相关的文件,确认目标指南存在于 `项目分析/AXIS主控制按钮调用链与WASM完善指南.md`,目标目录 `work/working9-执行过程按钮` 已存在,且根目录下还有 `gptlog-process/gpdlog.md`。
|
||||
3. 读取 `项目分析/AXIS主控制按钮调用链与WASM完善指南.md` 第一段内容,确认文档说明 AXIS 按钮真实链路为 `AXIS Python/Tk -> linuxcnc.command() -> EMC_TASK/EMC_JOINT NML -> task -> taskintf -> motion -> emcStatus -> linuxcnc.stat().poll()`。
|
||||
4. 继续读取指南后半部分,重点确认 Run 执行按钮的 LinuxCNC 语义:`task_run()` 先 `ensure_mode(MODE_AUTO)`,再发送 `AUTO_RUN`,task 侧通过 `EMC_TASK_PLAN_RUN` 设置 `interpState=READING`、清暂停/单步状态,并要求 machine ON、AUTO、已 homed 或 `no_force_homing`、程序已打开。
|
||||
5. 同时确认 Run 不是孤立按钮,必须与急停、上电、Home、Pause、Resume、Step 的状态链一起规划:ESTOP/RESET、Power ON、Manual Home、AUTO Run、Reading/Waiting、Pause/Resume/Step、Done/Error/Abort。
|
||||
6. 读取 `work/working8-暂停按钮/README.md`、`01-项目功能内容.md`、`02-项目程序开发详细步骤.md`、`04-任务矩阵.md`,参考既有 working8 文档组织方式和编号方式。
|
||||
7. 使用 `git status --short` 检查工作区,确认已有多处与本任务无关的修改和未跟踪内容;本次未回退、未修改这些无关变更,只在 `work/working9-执行过程按钮` 新建文档,并追加根日志。
|
||||
8. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/README.md`,写入主题、文档索引、当前推进入口和当前结论,明确 working9 以“执行 Run”按钮为主线,但覆盖完整执行控制链。
|
||||
9. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/01-项目功能内容.md`,写入项目目标、LinuxCNC 对标依据、必须覆盖范围、暂不覆盖范围、当前代码基线、主要差距、完成定义和风险提醒。
|
||||
10. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/02-项目程序开发详细步骤.md`,按步骤列出 Run 状态门禁表、program open/canonical plan 边界、`EMC_TASK_PLAN_RUN` 状态转换、Web store status 投影、Home 过程态、Pause/Resume/Step 回归、构建回归命令和记录关闭流程。
|
||||
11. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/03-推进台账.md`,建立每轮记录模板,并追加第 0 轮“文档初始化”记录,说明本轮目标、做了什么、改了哪些文件、验证了什么、结果证据和下一步。
|
||||
12. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/04-任务矩阵.md`,定义状态含义,并建立 `W9-000` 到 `W9-011` 任务矩阵,覆盖文档初始化、Run 门禁、Web policy、program open、Run 状态转换、motion 推进字段、Web status 投影、Home 前置、Pause/Resume/Step 回归、浏览器验收、构建同步和证据闭环。
|
||||
13. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/05-验收证据.md`,写入证据记录规则、第 0 轮证据和后续任务待补证据清单。
|
||||
14. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/06-决策记录.md`,记录三条初始决策:working9 以 Run 为主线但覆盖完整控制链;Web `runState` 只能作为 status 投影;G-code 到 motion 不再扩展 JS 正则 fallback。
|
||||
15. 使用 `find` 核对 `work/working9-执行过程按钮` 下已生成 7 个 Markdown 文件:README、01、02、03、04、05、06。
|
||||
16. 使用 `wc -l 'work/working9-执行过程按钮/'*.md` 统计新文档共 606 行,确认每个文档均非空。
|
||||
17. 使用 `sed` 抽查 README 和任务矩阵,确认索引、入口、`W9-000`、`W9-001` 等内容存在。
|
||||
18. 使用 `date '+%Y-%m-%d %H:%M %Z'` 获取当时检查时间为 `2026-07-07 16:42 EDT`。
|
||||
19. 发现初始写入台账和验收证据时误填为 `16:56 EDT`,随即使用 `apply_patch` 将 `03-推进台账.md` 和 `05-验收证据.md` 的时间戳修正为 `2026-07-07 16:42 EDT`。
|
||||
20. 再次使用 `find`、`rg`、`git status --short -- 'work/working9-执行过程按钮' 'gptlog-process/gpdlog.md'` 核对结果,确认 working9 新目录为未跟踪新增,根日志为已修改,且时间戳已无 `16:56` 残留。
|
||||
21. 使用 `apply_patch` 将本次创建 working9 文档体系的完整中文执行过程追加到 `gptlog-process/gpdlog.md`。
|
||||
22. 最后核对 `tail` 时发现日志文件末尾已有较早的 `2026-07-07 16:39` 记录,刚追加的 `16:42` 记录位于该记录之前,不符合“追加至文件末尾”的要求。
|
||||
23. 使用 `rg -n '^## 2026-07-07 16:(31|39|42)' gptlog-process/gpdlog.md` 定位相关日志标题行,确认 `16:42` 记录位于 `16:39` 记录之前。
|
||||
24. 使用 `apply_patch` 删除中间位置的 `16:42` 记录,并以当前时间 `2026-07-07 16:43 EDT` 将本次完整记录重新追加到文件末尾。
|
||||
|
||||
### 结论
|
||||
已在 `work/working9-执行过程按钮` 创建完整推进文档体系:`README.md`、`01-项目功能内容.md`、`02-项目程序开发详细步骤.md`、`03-推进台账.md`、`04-任务矩阵.md`、`05-验收证据.md`、`06-决策记录.md`。文档明确 working9 后续以 AXIS 风格 Run 执行按钮为主线,并把急停、上电、Home、Pause、Resume、Step 纳入 Run 的前置条件和执行过程状态链。任务矩阵已从 `W9-001` 开始安排后续可执行任务,便于继续按矩阵推进。
|
||||
|
||||
@@ -1781,7 +1781,13 @@ export function createSimulationStore(seed = {}) {
|
||||
{ type: "EMC_TASK_PLAN_PAUSE" },
|
||||
], {
|
||||
operatorMessage: "task/HAL program paused",
|
||||
preserveMachine: pausedMachine,
|
||||
}).then((status) => {
|
||||
if (isTaskHalStatusPaused(status)) {
|
||||
startTaskHalStatusLoop({
|
||||
batchSize: 1,
|
||||
operatorMessage: "task/HAL paused status monitor running",
|
||||
});
|
||||
}
|
||||
}).catch(() => {});
|
||||
break;
|
||||
}
|
||||
@@ -1858,7 +1864,6 @@ export function createSimulationStore(seed = {}) {
|
||||
{ type: "EMC_TASK_PLAN_RESUME" },
|
||||
], {
|
||||
operatorMessage: "task/HAL program resumed",
|
||||
preserveMachine: resumedMachine,
|
||||
}).then((status) => {
|
||||
if (shouldContinueTaskHalStatusLoop(state, status)) {
|
||||
startTaskHalStatusLoop({
|
||||
@@ -2274,6 +2279,9 @@ export function createSimulationStore(seed = {}) {
|
||||
};
|
||||
|
||||
const refreshAsyncKinematicsFrame = async ({ operatorMessage = state.operatorMessage } = {}) => {
|
||||
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) {
|
||||
return state.rtcpFrame;
|
||||
}
|
||||
if (state.desiredFrameSourceMode !== "source-derived-kinematics-wasm") {
|
||||
return state.rtcpFrame;
|
||||
}
|
||||
@@ -2296,6 +2304,14 @@ export function createSimulationStore(seed = {}) {
|
||||
if (state.asyncFrameRefreshSequence !== sequence) {
|
||||
return state.rtcpFrame;
|
||||
}
|
||||
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) {
|
||||
state = {
|
||||
...state,
|
||||
asyncFrameRefreshPending: false,
|
||||
};
|
||||
notify();
|
||||
return state.rtcpFrame;
|
||||
}
|
||||
const frame = buildRtcpFrame({
|
||||
axisPose: state.axisPose,
|
||||
activeLine: state.activeLine,
|
||||
@@ -2988,6 +3004,7 @@ export function createSimulationStore(seed = {}) {
|
||||
};
|
||||
|
||||
const scheduleAsyncKinematicsRefresh = () => {
|
||||
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) return null;
|
||||
if (state.asyncFrameRefreshPending) return null;
|
||||
if (state.desiredFrameSourceMode !== "source-derived-kinematics-wasm") return null;
|
||||
if (!state.kinematicsRuntime?.loaded || !isAsyncKinematicsRuntime(state.kinematicsRuntime)) return null;
|
||||
@@ -3779,43 +3796,44 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
const ui = status?.ui || {};
|
||||
const task = status?.task || {};
|
||||
const motion = status?.motionStatus?.motion || {};
|
||||
const pauseLock = state.taskHalPauseLock?.active === true ? state.taskHalPauseLock : null;
|
||||
const statusInterpState = normalizeTaskHalInterpState(ui.interpState || task.interpState);
|
||||
const statusMotionPaused = ui.motionPaused === true || motion.paused === true;
|
||||
const statusTaskPaused = ui.taskPaused === true || task.taskPaused === true;
|
||||
const statusPaused = statusInterpState === "paused" || statusMotionPaused || statusTaskPaused;
|
||||
const rawTaskState = normalizeTaskHalTaskState(ui.taskState || task.state);
|
||||
const taskState = preserveMachine?.powerOn && rawTaskState === "estop-reset"
|
||||
? "on"
|
||||
: rawTaskState;
|
||||
const taskMode = pauseLock
|
||||
const taskMode = statusPaused
|
||||
? programControlModeForMachine(state.machine)
|
||||
: normalizeLinuxCncTaskMode(preserveMachine?.mode || ui.taskMode || task.mode || state.machine.mode);
|
||||
const manualPanel = taskMode === "manual"
|
||||
? (preserveMachine?.manualPanel || state.machine.manualPanel || "manual")
|
||||
: null;
|
||||
const interpState = pauseLock
|
||||
const interpState = statusPaused
|
||||
? "paused"
|
||||
: Object.hasOwn(preserveMachine || {}, "interpState")
|
||||
? normalizeTaskHalInterpState(preserveMachine.interpState)
|
||||
: normalizeTaskHalInterpState(ui.interpState || task.interpState);
|
||||
: statusInterpState;
|
||||
const allHomed = Boolean(preserveMachine?.allHomed ?? state.machine.allHomed);
|
||||
const activeLine = state.programStartLine + Math.max(Number(ui.activeLine || 1) - 1, 0);
|
||||
const kinsType = resolveTaskHalKinsType(state, status, activeLine);
|
||||
const motionPaused = Boolean(pauseLock) ||
|
||||
ui.motionPaused === true ||
|
||||
motion.paused === true ||
|
||||
preserveMachine?.motionPaused === true;
|
||||
const taskPaused = ui.taskPaused === true || task.taskPaused === true || preserveMachine?.taskPaused === true;
|
||||
const paused = Boolean(pauseLock) || interpState === "paused" || motionPaused || taskPaused;
|
||||
const motionPaused = statusMotionPaused || preserveMachine?.motionPaused === true;
|
||||
const taskPaused = statusTaskPaused || preserveMachine?.taskPaused === true;
|
||||
const paused = interpState === "paused" || motionPaused || taskPaused;
|
||||
const interpResumeState = normalizeTaskHalInterpState(
|
||||
ui.interpResumeState ||
|
||||
task.interpResumeState ||
|
||||
(paused ? state.machine.interpResumeState || "reading" : interpState),
|
||||
);
|
||||
const axisPose = pauseLock?.axisPose
|
||||
? clampAxisPoseToProfile(pauseLock.axisPose, state.profile)
|
||||
: paused
|
||||
? clampAxisPoseToProfile(state.axisPose, state.profile)
|
||||
const displayPauseLock = paused && state.taskHalPauseLock?.active === true
|
||||
? state.taskHalPauseLock
|
||||
: null;
|
||||
const axisPose = displayPauseLock?.axisPose
|
||||
? clampAxisPoseToProfile(displayPauseLock.axisPose, state.profile)
|
||||
: preserveAxisPose
|
||||
? clampAxisPoseToProfile({ ...state.axisPose, ...preserveAxisPose }, state.profile)
|
||||
: resolveTaskHalAxisPose(state, status);
|
||||
: resolveTaskHalAxisPose(state, status, { paused, runState: state.runState });
|
||||
const currentVelocity = paused
|
||||
? 0
|
||||
: Number.isFinite(ui.currentVelocity)
|
||||
@@ -3841,9 +3859,9 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
const shouldTrackProgramPlayback = runState === "running"
|
||||
|| runState === "mdi"
|
||||
|| (runState === "complete" && (state.runState === "running" || state.runState === "mdi" || state.runState === "complete"));
|
||||
const taskHalSampleIndex = pauseLock
|
||||
const taskHalSampleIndex = displayPauseLock
|
||||
? clampNumber(
|
||||
pauseLock.sampleIndex,
|
||||
displayPauseLock.sampleIndex,
|
||||
0,
|
||||
Math.max(Number(state.programAxisPreviewPath?.samples?.length || state.programAxisPreviewPath?.sampleCount || 1) - 1, 0),
|
||||
)
|
||||
@@ -3857,15 +3875,15 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
const idleRuntimeFeedback = {
|
||||
...runtimeFeedback,
|
||||
sampleIndex: taskHalSampleIndex,
|
||||
motionIndex: pauseLock
|
||||
? Number(pauseLock.motionIndex || 0)
|
||||
motionIndex: displayPauseLock
|
||||
? Number(displayPauseLock.motionIndex || 0)
|
||||
: Number(state.programExecutionMotionIndex || runtimeFeedback.motionIndex || 0),
|
||||
...(paused ? {
|
||||
axisPose,
|
||||
tcp: {
|
||||
x: Number(state.tcpPose?.x ?? axisPose.x ?? 0),
|
||||
y: Number(state.tcpPose?.y ?? axisPose.y ?? 0),
|
||||
z: Number(state.tcpPose?.z ?? axisPose.z ?? 0),
|
||||
x: Number(displayPauseLock?.tcpPose?.x ?? state.tcpPose?.x ?? axisPose.x ?? 0),
|
||||
y: Number(displayPauseLock?.tcpPose?.y ?? state.tcpPose?.y ?? axisPose.y ?? 0),
|
||||
z: Number(displayPauseLock?.tcpPose?.z ?? state.tcpPose?.z ?? axisPose.z ?? 0),
|
||||
},
|
||||
} : {}),
|
||||
};
|
||||
@@ -3875,12 +3893,12 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
axisPose,
|
||||
kinsType,
|
||||
rtcpState: rtcpStateFromKinsType(kinsType),
|
||||
motionIndex: pauseLock ? Number(pauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
|
||||
motionIndex: displayPauseLock ? Number(displayPauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
|
||||
sampleIndex: taskHalSampleIndex,
|
||||
runtimeFeedback: {
|
||||
...runtimeFeedback,
|
||||
sampleIndex: taskHalSampleIndex,
|
||||
motionIndex: pauseLock ? Number(pauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
|
||||
motionIndex: displayPauseLock ? Number(displayPauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
|
||||
axisPose,
|
||||
currentVelocityMmPerMin: paused ? 0 : runtimeFeedback.currentVelocityMmPerMin,
|
||||
requestedVelocityMmPerMin: paused ? 0 : runtimeFeedback.requestedVelocityMmPerMin,
|
||||
@@ -3900,6 +3918,8 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
? createProgramUiExecution({
|
||||
...state,
|
||||
axisPose,
|
||||
tcpPose: displayPauseLock?.tcpPose || state.tcpPose,
|
||||
rtcpFrame: displayPauseLock?.rtcpFrame || state.rtcpFrame,
|
||||
kinsType: kinsType || state.kinsType,
|
||||
programExecutionSampleIndex: taskHalSampleIndex,
|
||||
programRuntimeFeedback: idleRuntimeFeedback,
|
||||
@@ -3909,7 +3929,7 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
const loopActive = state.taskHalStatusLoop?.active === true
|
||||
&& loopSequence !== null
|
||||
&& Number(state.taskHalStatusLoop.sequence) === Number(loopSequence)
|
||||
&& (runState === "running" || runState === "mdi");
|
||||
&& (runState === "running" || runState === "mdi" || runState === "paused");
|
||||
const nextTickCount = loopActive ? Number(state.taskHalStatusLoop.tickCount || 0) + 1 : Number(state.taskHalStatusLoop?.tickCount || 0);
|
||||
const feedbackHistory = [taskHalPlaybackPatch.programRuntimeFeedback, ...(state.programRuntimeFeedbackHistory || [])].slice(0, 100);
|
||||
const lineStatus = runState === "complete"
|
||||
@@ -3921,10 +3941,15 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
return {
|
||||
taskHalStatus: status,
|
||||
taskHalExecutionPending: false,
|
||||
taskHalPauseLock: pauseLock ? { ...pauseLock, lastStatusAt: new Date().toISOString() } : state.taskHalPauseLock,
|
||||
taskHalPauseLock: displayPauseLock ? { ...displayPauseLock, lastStatusAt: new Date().toISOString() } : null,
|
||||
taskHalFallbackReason: null,
|
||||
pendingJogCommand: null,
|
||||
...taskHalPlaybackPatch,
|
||||
...(displayPauseLock ? {
|
||||
tcpPose: displayPauseLock.tcpPose,
|
||||
rtcpFrame: displayPauseLock.rtcpFrame,
|
||||
dro: displayPauseLock.dro,
|
||||
} : {}),
|
||||
programExecutionSourceMode: "linuxcnc-task-motion-hal-wasm",
|
||||
machine: {
|
||||
...state.machine,
|
||||
@@ -3978,9 +4003,26 @@ function shouldContinueTaskHalStatusLoop(state = {}, status = {}) {
|
||||
const openedProgramLineCount = Number(task.openedSourceLineCount || task.openedLineCount || 1);
|
||||
const complete = interpState === "idle"
|
||||
&& Number(task.nextProgramLine || 0) >= openedProgramLineCount;
|
||||
if (paused) {
|
||||
return state.runState === "paused" ||
|
||||
state.machine?.interpState === "paused" ||
|
||||
state.machine?.taskPaused === true ||
|
||||
state.machine?.motionPaused === true;
|
||||
}
|
||||
return !aborted && !paused && !complete && (interpState === "reading" || taskMode === "mdi");
|
||||
}
|
||||
|
||||
function isTaskHalStatusPaused(status = {}) {
|
||||
const ui = status?.ui || {};
|
||||
const task = status?.task || {};
|
||||
const motion = status?.motionStatus?.motion || {};
|
||||
return normalizeTaskHalInterpState(ui.interpState || task.interpState) === "paused" ||
|
||||
ui.motionPaused === true ||
|
||||
ui.taskPaused === true ||
|
||||
motion.paused === true ||
|
||||
task.taskPaused === true;
|
||||
}
|
||||
|
||||
function isTaskHalRunPausedByOperator(state = {}) {
|
||||
return state.runState === "paused"
|
||||
|| state.machine?.interpState === "paused"
|
||||
@@ -4024,6 +4066,8 @@ function createTaskHalPauseLock(state = {}, source = "pause") {
|
||||
y: Number(state.tcpPose?.y || 0),
|
||||
z: Number(state.tcpPose?.z || 0),
|
||||
},
|
||||
rtcpFrame: state.rtcpFrame,
|
||||
dro: state.dro,
|
||||
semanticBoundary: "linuxcnc_axis_task_pauseresume_freezes_task_hal_status_until_resume",
|
||||
};
|
||||
}
|
||||
@@ -4104,7 +4148,7 @@ function programMotionAtOrBeforeLine(state, activeLine) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
function resolveTaskHalAxisPose(state, status) {
|
||||
function resolveTaskHalAxisPose(state, status, { paused = false, runState = state.runState } = {}) {
|
||||
const ui = status?.ui || {};
|
||||
const axisPose = ui.axisPose;
|
||||
if (!axisPose || typeof axisPose !== "object") {
|
||||
@@ -4127,6 +4171,10 @@ function resolveTaskHalAxisPose(state, status) {
|
||||
return addAxisDelta(state.axisPose, ui.axisPoseDelta, state.profile);
|
||||
}
|
||||
|
||||
if (!paused && runState !== "running" && runState !== "mdi" && !isJogStatus(status)) {
|
||||
return state.axisPose;
|
||||
}
|
||||
|
||||
if (!ui.axisPoseFrame && wouldResetNonZeroPoseToLocalZero(state.axisPose, axisPose)) {
|
||||
return state.axisPose;
|
||||
}
|
||||
|
||||
@@ -1781,7 +1781,13 @@ export function createSimulationStore(seed = {}) {
|
||||
{ type: "EMC_TASK_PLAN_PAUSE" },
|
||||
], {
|
||||
operatorMessage: "task/HAL program paused",
|
||||
preserveMachine: pausedMachine,
|
||||
}).then((status) => {
|
||||
if (isTaskHalStatusPaused(status)) {
|
||||
startTaskHalStatusLoop({
|
||||
batchSize: 1,
|
||||
operatorMessage: "task/HAL paused status monitor running",
|
||||
});
|
||||
}
|
||||
}).catch(() => {});
|
||||
break;
|
||||
}
|
||||
@@ -1858,7 +1864,6 @@ export function createSimulationStore(seed = {}) {
|
||||
{ type: "EMC_TASK_PLAN_RESUME" },
|
||||
], {
|
||||
operatorMessage: "task/HAL program resumed",
|
||||
preserveMachine: resumedMachine,
|
||||
}).then((status) => {
|
||||
if (shouldContinueTaskHalStatusLoop(state, status)) {
|
||||
startTaskHalStatusLoop({
|
||||
@@ -2274,6 +2279,9 @@ export function createSimulationStore(seed = {}) {
|
||||
};
|
||||
|
||||
const refreshAsyncKinematicsFrame = async ({ operatorMessage = state.operatorMessage } = {}) => {
|
||||
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) {
|
||||
return state.rtcpFrame;
|
||||
}
|
||||
if (state.desiredFrameSourceMode !== "source-derived-kinematics-wasm") {
|
||||
return state.rtcpFrame;
|
||||
}
|
||||
@@ -2296,6 +2304,14 @@ export function createSimulationStore(seed = {}) {
|
||||
if (state.asyncFrameRefreshSequence !== sequence) {
|
||||
return state.rtcpFrame;
|
||||
}
|
||||
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) {
|
||||
state = {
|
||||
...state,
|
||||
asyncFrameRefreshPending: false,
|
||||
};
|
||||
notify();
|
||||
return state.rtcpFrame;
|
||||
}
|
||||
const frame = buildRtcpFrame({
|
||||
axisPose: state.axisPose,
|
||||
activeLine: state.activeLine,
|
||||
@@ -2988,6 +3004,7 @@ export function createSimulationStore(seed = {}) {
|
||||
};
|
||||
|
||||
const scheduleAsyncKinematicsRefresh = () => {
|
||||
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) return null;
|
||||
if (state.asyncFrameRefreshPending) return null;
|
||||
if (state.desiredFrameSourceMode !== "source-derived-kinematics-wasm") return null;
|
||||
if (!state.kinematicsRuntime?.loaded || !isAsyncKinematicsRuntime(state.kinematicsRuntime)) return null;
|
||||
@@ -3779,43 +3796,44 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
const ui = status?.ui || {};
|
||||
const task = status?.task || {};
|
||||
const motion = status?.motionStatus?.motion || {};
|
||||
const pauseLock = state.taskHalPauseLock?.active === true ? state.taskHalPauseLock : null;
|
||||
const statusInterpState = normalizeTaskHalInterpState(ui.interpState || task.interpState);
|
||||
const statusMotionPaused = ui.motionPaused === true || motion.paused === true;
|
||||
const statusTaskPaused = ui.taskPaused === true || task.taskPaused === true;
|
||||
const statusPaused = statusInterpState === "paused" || statusMotionPaused || statusTaskPaused;
|
||||
const rawTaskState = normalizeTaskHalTaskState(ui.taskState || task.state);
|
||||
const taskState = preserveMachine?.powerOn && rawTaskState === "estop-reset"
|
||||
? "on"
|
||||
: rawTaskState;
|
||||
const taskMode = pauseLock
|
||||
const taskMode = statusPaused
|
||||
? programControlModeForMachine(state.machine)
|
||||
: normalizeLinuxCncTaskMode(preserveMachine?.mode || ui.taskMode || task.mode || state.machine.mode);
|
||||
const manualPanel = taskMode === "manual"
|
||||
? (preserveMachine?.manualPanel || state.machine.manualPanel || "manual")
|
||||
: null;
|
||||
const interpState = pauseLock
|
||||
const interpState = statusPaused
|
||||
? "paused"
|
||||
: Object.hasOwn(preserveMachine || {}, "interpState")
|
||||
? normalizeTaskHalInterpState(preserveMachine.interpState)
|
||||
: normalizeTaskHalInterpState(ui.interpState || task.interpState);
|
||||
: statusInterpState;
|
||||
const allHomed = Boolean(preserveMachine?.allHomed ?? state.machine.allHomed);
|
||||
const activeLine = state.programStartLine + Math.max(Number(ui.activeLine || 1) - 1, 0);
|
||||
const kinsType = resolveTaskHalKinsType(state, status, activeLine);
|
||||
const motionPaused = Boolean(pauseLock) ||
|
||||
ui.motionPaused === true ||
|
||||
motion.paused === true ||
|
||||
preserveMachine?.motionPaused === true;
|
||||
const taskPaused = ui.taskPaused === true || task.taskPaused === true || preserveMachine?.taskPaused === true;
|
||||
const paused = Boolean(pauseLock) || interpState === "paused" || motionPaused || taskPaused;
|
||||
const motionPaused = statusMotionPaused || preserveMachine?.motionPaused === true;
|
||||
const taskPaused = statusTaskPaused || preserveMachine?.taskPaused === true;
|
||||
const paused = interpState === "paused" || motionPaused || taskPaused;
|
||||
const interpResumeState = normalizeTaskHalInterpState(
|
||||
ui.interpResumeState ||
|
||||
task.interpResumeState ||
|
||||
(paused ? state.machine.interpResumeState || "reading" : interpState),
|
||||
);
|
||||
const axisPose = pauseLock?.axisPose
|
||||
? clampAxisPoseToProfile(pauseLock.axisPose, state.profile)
|
||||
: paused
|
||||
? clampAxisPoseToProfile(state.axisPose, state.profile)
|
||||
const displayPauseLock = paused && state.taskHalPauseLock?.active === true
|
||||
? state.taskHalPauseLock
|
||||
: null;
|
||||
const axisPose = displayPauseLock?.axisPose
|
||||
? clampAxisPoseToProfile(displayPauseLock.axisPose, state.profile)
|
||||
: preserveAxisPose
|
||||
? clampAxisPoseToProfile({ ...state.axisPose, ...preserveAxisPose }, state.profile)
|
||||
: resolveTaskHalAxisPose(state, status);
|
||||
: resolveTaskHalAxisPose(state, status, { paused, runState: state.runState });
|
||||
const currentVelocity = paused
|
||||
? 0
|
||||
: Number.isFinite(ui.currentVelocity)
|
||||
@@ -3841,9 +3859,9 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
const shouldTrackProgramPlayback = runState === "running"
|
||||
|| runState === "mdi"
|
||||
|| (runState === "complete" && (state.runState === "running" || state.runState === "mdi" || state.runState === "complete"));
|
||||
const taskHalSampleIndex = pauseLock
|
||||
const taskHalSampleIndex = displayPauseLock
|
||||
? clampNumber(
|
||||
pauseLock.sampleIndex,
|
||||
displayPauseLock.sampleIndex,
|
||||
0,
|
||||
Math.max(Number(state.programAxisPreviewPath?.samples?.length || state.programAxisPreviewPath?.sampleCount || 1) - 1, 0),
|
||||
)
|
||||
@@ -3857,15 +3875,15 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
const idleRuntimeFeedback = {
|
||||
...runtimeFeedback,
|
||||
sampleIndex: taskHalSampleIndex,
|
||||
motionIndex: pauseLock
|
||||
? Number(pauseLock.motionIndex || 0)
|
||||
motionIndex: displayPauseLock
|
||||
? Number(displayPauseLock.motionIndex || 0)
|
||||
: Number(state.programExecutionMotionIndex || runtimeFeedback.motionIndex || 0),
|
||||
...(paused ? {
|
||||
axisPose,
|
||||
tcp: {
|
||||
x: Number(state.tcpPose?.x ?? axisPose.x ?? 0),
|
||||
y: Number(state.tcpPose?.y ?? axisPose.y ?? 0),
|
||||
z: Number(state.tcpPose?.z ?? axisPose.z ?? 0),
|
||||
x: Number(displayPauseLock?.tcpPose?.x ?? state.tcpPose?.x ?? axisPose.x ?? 0),
|
||||
y: Number(displayPauseLock?.tcpPose?.y ?? state.tcpPose?.y ?? axisPose.y ?? 0),
|
||||
z: Number(displayPauseLock?.tcpPose?.z ?? state.tcpPose?.z ?? axisPose.z ?? 0),
|
||||
},
|
||||
} : {}),
|
||||
};
|
||||
@@ -3875,12 +3893,12 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
axisPose,
|
||||
kinsType,
|
||||
rtcpState: rtcpStateFromKinsType(kinsType),
|
||||
motionIndex: pauseLock ? Number(pauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
|
||||
motionIndex: displayPauseLock ? Number(displayPauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
|
||||
sampleIndex: taskHalSampleIndex,
|
||||
runtimeFeedback: {
|
||||
...runtimeFeedback,
|
||||
sampleIndex: taskHalSampleIndex,
|
||||
motionIndex: pauseLock ? Number(pauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
|
||||
motionIndex: displayPauseLock ? Number(displayPauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
|
||||
axisPose,
|
||||
currentVelocityMmPerMin: paused ? 0 : runtimeFeedback.currentVelocityMmPerMin,
|
||||
requestedVelocityMmPerMin: paused ? 0 : runtimeFeedback.requestedVelocityMmPerMin,
|
||||
@@ -3900,6 +3918,8 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
? createProgramUiExecution({
|
||||
...state,
|
||||
axisPose,
|
||||
tcpPose: displayPauseLock?.tcpPose || state.tcpPose,
|
||||
rtcpFrame: displayPauseLock?.rtcpFrame || state.rtcpFrame,
|
||||
kinsType: kinsType || state.kinsType,
|
||||
programExecutionSampleIndex: taskHalSampleIndex,
|
||||
programRuntimeFeedback: idleRuntimeFeedback,
|
||||
@@ -3909,7 +3929,7 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
const loopActive = state.taskHalStatusLoop?.active === true
|
||||
&& loopSequence !== null
|
||||
&& Number(state.taskHalStatusLoop.sequence) === Number(loopSequence)
|
||||
&& (runState === "running" || runState === "mdi");
|
||||
&& (runState === "running" || runState === "mdi" || runState === "paused");
|
||||
const nextTickCount = loopActive ? Number(state.taskHalStatusLoop.tickCount || 0) + 1 : Number(state.taskHalStatusLoop?.tickCount || 0);
|
||||
const feedbackHistory = [taskHalPlaybackPatch.programRuntimeFeedback, ...(state.programRuntimeFeedbackHistory || [])].slice(0, 100);
|
||||
const lineStatus = runState === "complete"
|
||||
@@ -3921,10 +3941,15 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
return {
|
||||
taskHalStatus: status,
|
||||
taskHalExecutionPending: false,
|
||||
taskHalPauseLock: pauseLock ? { ...pauseLock, lastStatusAt: new Date().toISOString() } : state.taskHalPauseLock,
|
||||
taskHalPauseLock: displayPauseLock ? { ...displayPauseLock, lastStatusAt: new Date().toISOString() } : null,
|
||||
taskHalFallbackReason: null,
|
||||
pendingJogCommand: null,
|
||||
...taskHalPlaybackPatch,
|
||||
...(displayPauseLock ? {
|
||||
tcpPose: displayPauseLock.tcpPose,
|
||||
rtcpFrame: displayPauseLock.rtcpFrame,
|
||||
dro: displayPauseLock.dro,
|
||||
} : {}),
|
||||
programExecutionSourceMode: "linuxcnc-task-motion-hal-wasm",
|
||||
machine: {
|
||||
...state.machine,
|
||||
@@ -3978,9 +4003,26 @@ function shouldContinueTaskHalStatusLoop(state = {}, status = {}) {
|
||||
const openedProgramLineCount = Number(task.openedSourceLineCount || task.openedLineCount || 1);
|
||||
const complete = interpState === "idle"
|
||||
&& Number(task.nextProgramLine || 0) >= openedProgramLineCount;
|
||||
if (paused) {
|
||||
return state.runState === "paused" ||
|
||||
state.machine?.interpState === "paused" ||
|
||||
state.machine?.taskPaused === true ||
|
||||
state.machine?.motionPaused === true;
|
||||
}
|
||||
return !aborted && !paused && !complete && (interpState === "reading" || taskMode === "mdi");
|
||||
}
|
||||
|
||||
function isTaskHalStatusPaused(status = {}) {
|
||||
const ui = status?.ui || {};
|
||||
const task = status?.task || {};
|
||||
const motion = status?.motionStatus?.motion || {};
|
||||
return normalizeTaskHalInterpState(ui.interpState || task.interpState) === "paused" ||
|
||||
ui.motionPaused === true ||
|
||||
ui.taskPaused === true ||
|
||||
motion.paused === true ||
|
||||
task.taskPaused === true;
|
||||
}
|
||||
|
||||
function isTaskHalRunPausedByOperator(state = {}) {
|
||||
return state.runState === "paused"
|
||||
|| state.machine?.interpState === "paused"
|
||||
@@ -4024,6 +4066,8 @@ function createTaskHalPauseLock(state = {}, source = "pause") {
|
||||
y: Number(state.tcpPose?.y || 0),
|
||||
z: Number(state.tcpPose?.z || 0),
|
||||
},
|
||||
rtcpFrame: state.rtcpFrame,
|
||||
dro: state.dro,
|
||||
semanticBoundary: "linuxcnc_axis_task_pauseresume_freezes_task_hal_status_until_resume",
|
||||
};
|
||||
}
|
||||
@@ -4104,7 +4148,7 @@ function programMotionAtOrBeforeLine(state, activeLine) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
function resolveTaskHalAxisPose(state, status) {
|
||||
function resolveTaskHalAxisPose(state, status, { paused = false, runState = state.runState } = {}) {
|
||||
const ui = status?.ui || {};
|
||||
const axisPose = ui.axisPose;
|
||||
if (!axisPose || typeof axisPose !== "object") {
|
||||
@@ -4127,6 +4171,10 @@ function resolveTaskHalAxisPose(state, status) {
|
||||
return addAxisDelta(state.axisPose, ui.axisPoseDelta, state.profile);
|
||||
}
|
||||
|
||||
if (!paused && runState !== "running" && runState !== "mdi" && !isJogStatus(status)) {
|
||||
return state.axisPose;
|
||||
}
|
||||
|
||||
if (!ui.axisPoseFrame && wouldResetNonZeroPoseToLocalZero(state.axisPose, axisPose)) {
|
||||
return state.axisPose;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 221 KiB |
|
After Width: | Height: | Size: 221 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 219 KiB |