Complete XYZAC LinuxCNC WASM parity and smooth 3D preview
BIN
doc-g/5axis-rtcp-running.png
Normal file
|
After Width: | Height: | Size: 186 KiB |
62
doc-g/5axis-rtcp-shortcut.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# 五轴 RTCP 案例快捷方式
|
||||
|
||||
快捷方式:[`../run-5axis-rtcp.sh`](../run-5axis-rtcp.sh)
|
||||
|
||||
默认执行:
|
||||
|
||||
```bash
|
||||
./run-5axis-rtcp.sh
|
||||
```
|
||||
|
||||
默认案例是:
|
||||
|
||||
```text
|
||||
linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini
|
||||
```
|
||||
|
||||
它使用 `XYZAC` 五轴表旋转/倾斜(TRT)拓扑、`xyzac-trt-kins` 和 Vismach `xyzac-trt-gui`。默认演示程序是:
|
||||
|
||||
```text
|
||||
linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins.ngc
|
||||
```
|
||||
|
||||
该顶层演示调用 `remap_subs/xyzac_switchkins_sub.ngc`;其螺旋插补子程序 `remap_subs/helix_ac.ngc` 使用:
|
||||
|
||||
```gcode
|
||||
M428 ;TCP:xyzac
|
||||
```
|
||||
|
||||
`M428` 通过 remap 将 `motion.switchkins-type` 切换到 XYZAC TCP/RTCP 运动学;`helix_ac.ngc` 在需要机床坐标定位时使用 `M429` 切回 identity,再用 `M428` 进入 TCP。配置的 `M430` 进入 userk。AXIS 右侧的 `TCP:XYZAC` 按钮也提供同一切换入口。
|
||||
|
||||
## XYZBC 变体
|
||||
|
||||
快捷方式接受可选配置名:
|
||||
|
||||
```bash
|
||||
./run-5axis-rtcp.sh xyzbc-trt.ini
|
||||
```
|
||||
|
||||
该变体使用 `XYZBC` 和 `xyzbc-trt-kins`,默认程序为 `demos/xyzbc_switchkins.ngc`。
|
||||
|
||||
## 启动证据
|
||||
|
||||

|
||||
|
||||
实测启动结果:
|
||||
|
||||
- AXIS 标题:`xyzac_switchkins.ngc - AXIS 2.10.0~pre1 on sim-xyzac-trt-kins (switchkins)`。
|
||||
- 右侧 `SWITCHKINS` 面板显示 `TCP:XYZAC`。
|
||||
- Vismach 预览显示五轴机床和多轴刀具轨迹。
|
||||
- LinuxCNC 状态接口返回 `file=./demos/xyzac_switchkins.ngc`,`COORDINATES=XYZAC`。
|
||||
- 当前环境使用 POSIX non-realtime 回退;这验证的是仿真配置和 RTCP 路径,不是实时硬件性能。
|
||||
|
||||
## 来源依据
|
||||
|
||||
- `linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini`
|
||||
- `linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins.ngc`
|
||||
- `linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/xyzac_switchkins_sub.ngc`
|
||||
- `linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/helix_ac.ngc`
|
||||
- `linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc`
|
||||
- `linuxcnc/src/emc/kinematics/xyzac-trt-kins.c`
|
||||
- `linuxcnc/src/emc/kinematics/trtfuncs.c`
|
||||
- `linuxcnc/docs/src/motion/5-axis-kinematics.adoc`
|
||||
29
doc-g/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# GMOCCAPY 调查资料
|
||||
|
||||
本目录保存本次 GMOCCAPY 实机仿真检查的截图、文件清单、源码分析和加载测试证据。后续与本案例有关的分析文档继续写入 `./doc-g`,不把临时调查结论散落到项目根目录。
|
||||
|
||||
## 入口
|
||||
|
||||
- [五轴 RTCP 文档集](../doc-rtcp/README.md)
|
||||
- [HTML 图文使用说明](generate.html)
|
||||
- [五轴 RTCP 快捷方式](5axis-rtcp-shortcut.md)
|
||||
- [控制面板数据流与控制流](gmoccapy-control-panel-data-control-flow.md)
|
||||
- [GMOCCAPY 分析报告](gmoccapy-analysis.md)
|
||||
- [逐文件清单](gmoccapy-file-inventory.md)
|
||||
- [机器可读清单](gmoccapy-file-inventory.json)
|
||||
|
||||
## 截图
|
||||
|
||||
- `gmoccapy-important-changes.png`:用户提供的首次启动提示。
|
||||
- `gmoccapy-important-changes-v2.png`:本轮新增的用户截图证据,已与系统 GMOCCAPY 3.5.2 源码逐项对照。
|
||||
- `gmoccapy-before-load.png`:主界面尚未加载程序。
|
||||
- `gmoccapy-after-popup-close.png`:关闭提示后的主界面。
|
||||
- `gmoccapy-load-file-page.png`:实际打开的程序文件选择页,能看到 `.ngc` 文件。
|
||||
- `gmoccapy-program-loaded.png`:通过 LinuxCNC API 加载 `3dtest.ngc` 后的路径和刀具轨迹。
|
||||
|
||||
## 可复现命令
|
||||
|
||||
```bash
|
||||
./run-gmoccapy.sh
|
||||
python3 doc-g/generate_inventory.py
|
||||
```
|
||||
264
doc-g/generate.html
Normal file
@@ -0,0 +1,264 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>GMOCCAPY 仿真使用说明</title>
|
||||
<style>
|
||||
:root {
|
||||
--ink: #1e293b;
|
||||
--muted: #64748b;
|
||||
--line: #dbe3ec;
|
||||
--paper: #ffffff;
|
||||
--wash: #f4f7fb;
|
||||
--blue: #155e9a;
|
||||
--blue-soft: #e8f2fb;
|
||||
--red: #d62828;
|
||||
--green: #18864b;
|
||||
--shadow: 0 12px 28px rgba(15, 23, 42, .08);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
background: var(--wash);
|
||||
font: 16px/1.65 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
}
|
||||
a { color: var(--blue); }
|
||||
.hero {
|
||||
padding: 52px max(24px, calc((100vw - 1180px) / 2));
|
||||
color: #fff;
|
||||
background: #102a43;
|
||||
border-bottom: 5px solid #d62828;
|
||||
}
|
||||
.hero h1 { max-width: 760px; margin: 0; font-size: clamp(2rem, 4vw, 3.5rem); line-height: 1.12; }
|
||||
.hero p { max-width: 720px; margin: 18px 0 0; color: #d8e6f2; font-size: 1.08rem; }
|
||||
.layout { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 28px; max-width: 1180px; margin: 30px auto; padding: 0 24px 60px; }
|
||||
.toc { position: sticky; top: 20px; align-self: start; padding: 18px; background: var(--paper); border: 1px solid var(--line); border-radius: 8px; box-shadow: var(--shadow); }
|
||||
.toc strong { display: block; margin-bottom: 10px; }
|
||||
.toc a { display: block; padding: 5px 0; text-decoration: none; font-size: .94rem; }
|
||||
main { min-width: 0; }
|
||||
section { margin-bottom: 32px; padding: 28px; background: var(--paper); border: 1px solid var(--line); border-radius: 8px; box-shadow: var(--shadow); }
|
||||
h2 { margin: 0 0 14px; color: #123b5d; font-size: 1.7rem; line-height: 1.2; }
|
||||
h3 { margin: 24px 0 8px; color: #234e70; font-size: 1.16rem; }
|
||||
p { margin: 10px 0; }
|
||||
.notice { padding: 16px 18px; border-left: 5px solid var(--red); background: #fff5f5; }
|
||||
.success { padding: 16px 18px; border-left: 5px solid var(--green); background: #effaf3; }
|
||||
.steps { display: grid; gap: 12px; margin: 18px 0 0; padding: 0; list-style: none; counter-reset: step; }
|
||||
.steps li { position: relative; padding: 14px 18px 14px 58px; border: 1px solid var(--line); border-radius: 6px; background: #fbfdff; counter-increment: step; }
|
||||
.steps li::before { content: counter(step); position: absolute; left: 17px; top: 13px; display: grid; place-items: center; width: 28px; height: 28px; color: #fff; background: var(--blue); border-radius: 50%; font-weight: 700; }
|
||||
code { padding: 2px 5px; background: #eef2f7; border-radius: 4px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em; }
|
||||
pre { overflow: auto; padding: 14px 16px; color: #e5edf5; background: #172b3d; border-radius: 6px; }
|
||||
.figure { margin: 22px 0 4px; }
|
||||
.annotated { position: relative; overflow: hidden; border: 1px solid #bbc8d5; background: #111; border-radius: 6px; }
|
||||
.annotated img { display: block; width: 100%; height: auto; }
|
||||
.annotated svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
||||
.annotated .arrow { stroke: var(--red); stroke-width: 8; fill: none; stroke-linecap: round; stroke-linejoin: round; }
|
||||
.annotated .arrow-thin { stroke-width: 5; }
|
||||
.annotated .label { fill: #fff; stroke: var(--red); stroke-width: 5; paint-order: stroke; font: 700 30px system-ui, sans-serif; }
|
||||
figcaption { margin-top: 8px; color: var(--muted); font-size: .92rem; }
|
||||
.two-col { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
|
||||
.fact { padding: 16px; border: 1px solid var(--line); border-radius: 6px; background: #fbfdff; }
|
||||
.fact strong { display: block; margin-bottom: 5px; color: #123b5d; }
|
||||
.button-chain { display: grid; gap: 8px; max-width: 760px; margin: 20px 0; }
|
||||
.chain-item { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: center; padding: 12px 14px; border: 2px solid #c8d8e8; border-radius: 6px; background: #f8fbfe; }
|
||||
.chain-item .badge { display: grid; place-items: center; width: 30px; height: 30px; color: #fff; background: var(--red); border-radius: 50%; font-weight: 800; }
|
||||
.chain-item small { display: block; color: var(--muted); }
|
||||
.chain-arrow { color: var(--red); font-size: 1.8rem; font-weight: 800; line-height: .8; text-align: center; }
|
||||
.auto-panel { position: relative; max-width: 760px; margin: 20px 0; padding: 18px; overflow: hidden; border: 2px solid #9fb4c8; border-radius: 6px; background: #e9eef3; }
|
||||
.auto-panel-title { margin-bottom: 12px; color: #123b5d; font-weight: 800; }
|
||||
.auto-buttons { display: grid; grid-template-columns: repeat(6, minmax(82px, 1fr)); gap: 9px; align-items: stretch; }
|
||||
.auto-btn { min-height: 104px; display: grid; place-items: center; align-content: center; gap: 5px; padding: 9px 5px; border: 1px solid #b7c4cf; border-radius: 6px; background: #fff; text-align: center; }
|
||||
.auto-btn img { width: 38px; height: 38px; object-fit: contain; }
|
||||
.auto-btn.target { border: 4px solid var(--red); background: #fff1f1; }
|
||||
.auto-btn code { padding: 1px 3px; font-size: .72rem; }
|
||||
.auto-panel svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
||||
.auto-panel .arrow { stroke: var(--red); stroke-width: 6; fill: none; stroke-linecap: round; stroke-linejoin: round; }
|
||||
.auto-panel .label { fill: #fff; stroke: var(--red); stroke-width: 4; paint-order: stroke; font: 800 22px system-ui, sans-serif; }
|
||||
@media (max-width: 650px) { .auto-buttons { grid-template-columns: repeat(3, minmax(82px, 1fr)); } }
|
||||
table { width: 100%; border-collapse: collapse; font-size: .94rem; }
|
||||
th, td { padding: 9px 10px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
|
||||
th { color: #123b5d; background: #f0f5fa; }
|
||||
.footer { max-width: 1180px; margin: 0 auto; padding: 0 24px 44px; color: var(--muted); font-size: .9rem; }
|
||||
@media (max-width: 820px) {
|
||||
.layout { display: block; margin-top: 18px; }
|
||||
.toc { position: static; margin-bottom: 18px; }
|
||||
section { padding: 20px; }
|
||||
.two-col { grid-template-columns: 1fr; }
|
||||
.hero { padding: 38px 24px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="hero">
|
||||
<h1>GMOCCAPY 仿真使用说明</h1>
|
||||
<p>从首次启动提示、进入 AUTO、选择 G-code,到确认程序已加载。红色箭头表示需要点击的位置;本手册基于当前工作区的实际 GMOCCAPY 截图和源码分析。</p>
|
||||
</header>
|
||||
|
||||
<div class="layout">
|
||||
<nav class="toc" aria-label="目录">
|
||||
<strong>本页导航</strong>
|
||||
<a href="#quick">快速流程</a>
|
||||
<a href="#popup">关闭首次提示</a>
|
||||
<a href="#auto">进入 AUTO</a>
|
||||
<a href="#select">选择程序</a>
|
||||
<a href="#verify">确认加载</a>
|
||||
<a href="#trouble">常见问题</a>
|
||||
<a href="#evidence">文件与证据</a>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<section id="quick">
|
||||
<h2>快速流程</h2>
|
||||
<ol class="steps">
|
||||
<li>启动项目快捷方式:<code>./run-gmoccapy.sh</code>。</li>
|
||||
<li>首次弹窗出现时,点击右侧 <strong>X</strong>;可选中“Don't show this again”。</li>
|
||||
<li>确认机器上电并完成 homing,然后点击右侧第三个模式按钮进入 AUTO。</li>
|
||||
<li>进入 AUTO 后,在 AUTO 按钮组中点击“打开文件”图标,选择 <code>.ngc</code> 文件。</li>
|
||||
<li>点击文件选择页底部的绿色对勾确认,检查顶部文件路径和刀具轨迹。</li>
|
||||
<li>加载不会自动运行;确认安全后再点击 Run,或使用 <code>R</code> 快捷键。</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section id="popup">
|
||||
<h2>1. 关闭首次启动提示</h2>
|
||||
<div class="notice"><strong>先处理这个窗口。</strong> 用户提供的“Important change(s)”是模态提示,会挡住 GMOCCAPY 主界面;在它关闭前,底层 AUTO 和加载按钮都无法正常点击。</div>
|
||||
<figure class="figure">
|
||||
<div class="annotated">
|
||||
<img src="gmoccapy-important-changes.png" alt="GMOCCAPY Important changes 首次启动提示">
|
||||
<svg viewBox="0 0 697 837" aria-hidden="true">
|
||||
<defs><marker id="arrow-popup" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M0,0 L12,6 L0,12 z" fill="#d62828"/></marker></defs>
|
||||
<path class="arrow" marker-end="url(#arrow-popup)" d="M420 260 C505 285 574 340 637 399"/>
|
||||
<text class="label" x="385" y="240">1 关闭提示</text>
|
||||
</svg>
|
||||
</div>
|
||||
<figcaption>箭头指向右侧 X。也可以勾选底部“Don't show this again”,避免每次启动重复出现。</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<section id="auto">
|
||||
<h2>2. 进入 AUTO 模式</h2>
|
||||
<p><strong>关键点:</strong>你要找的“加载程序”按钮不在当前 MANUAL/JOG 页面。它只有在切换到 AUTO 后,才会被放入 AUTO 专用按钮组。</p>
|
||||
<figure class="figure">
|
||||
<div class="annotated">
|
||||
<img src="gmoccapy-after-popup-close.png" alt="关闭提示后的 GMOCCAPY 主界面">
|
||||
<svg viewBox="0 0 1960 1500" aria-hidden="true">
|
||||
<defs><marker id="arrow-auto" markerWidth="18" markerHeight="18" refX="15" refY="9" orient="auto"><path d="M0,0 L18,9 L0,18 z" fill="#d62828"/></marker></defs>
|
||||
<path class="arrow" marker-end="url(#arrow-auto)" d="M1415 235 C1530 270 1640 330 1710 390"/>
|
||||
<text class="label" x="1210" y="220">2 点击 AUTO</text>
|
||||
</svg>
|
||||
</div>
|
||||
<figcaption>箭头指向右侧竖向模式按钮中的 AUTO 图标。若该按钮灰色,先处理“急停、上电、homing”状态。</figcaption>
|
||||
</figure>
|
||||
<h3>具体点击顺序</h3>
|
||||
<ol class="steps">
|
||||
<li><strong>先看右侧竖条。</strong>从上到下通常是急停、上电、AUTO、MDI 等图标。点击第三个“彩色小方块/机床箭头”图标,不要点击 MDI 计算器图标。</li>
|
||||
<li><strong>等待页面切换。</strong>右侧或底部按钮区域会从手动点动按钮切换为 AUTO 按钮组。窗口大小、主题和侧面板会改变图标的排列,但按钮的 tooltip 仍是 <code>Load a new program</code>。</li>
|
||||
<li><strong>再点打开文件按钮。</strong>在 AUTO 按钮组中寻找文件夹/打开文件图标。它的源码 ID 是 <code>btn_load</code>,不是文件选择页里的绿色对勾。</li>
|
||||
<li><strong>看到文件网格才算进入选择界面。</strong>点击 <code>btn_load</code> 后,GMOCCAPY 才会打开“选择 G-code 程序”界面;源码函数是 <code>on_btn_load_clicked()</code>,它把按钮页切换到文件选择页。</li>
|
||||
</ol>
|
||||
<div class="button-chain" aria-label="打开 G-code 文件界面的按钮链">
|
||||
<div class="chain-item"><span class="badge">A</span><div><strong>右侧第三个 AUTO 图标</strong><small><code>rbt_auto</code>:先切换按钮组。</small></div></div>
|
||||
<div class="chain-arrow" aria-hidden="true">↓</div>
|
||||
<div class="chain-item"><span class="badge">B</span><div><strong>AUTO 页面中的打开文件图标</strong><small><code>btn_load</code>,tooltip 为 <code>Load a new program</code>;点击它才打开文件选择界面。</small></div></div>
|
||||
<div class="chain-arrow" aria-hidden="true">↓</div>
|
||||
<div class="chain-item"><span class="badge">C</span><div><strong>文件网格和底部绿色对勾</strong><small><code>btn_select</code>:选择文件后确认返回路径。</small></div></div>
|
||||
</div>
|
||||
<h3>在 AUTO 页面里,具体就是这个打开文件图标</h3>
|
||||
<p>下图不是另一个入口,而是从 GMOCCAPY Glade 的 <code>hbtb_auto</code> 按钮组和当前 icon theme 放大的控件图。红框和箭头明确标出 <code>btn_load</code>;鼠标悬停时 tooltip 是 <code>Load a new program</code>。</p>
|
||||
<div class="auto-panel" role="img" aria-label="AUTO 页面按钮组中 btn_load 打开文件图标的源码对应图">
|
||||
<div class="auto-panel-title">AUTO 页面按钮组:hbtb_auto</div>
|
||||
<div class="auto-buttons">
|
||||
<div class="auto-btn target"><img src="gmoccapy-open-file-icon.png" alt="打开文件图标"><strong>打开文件</strong><code>btn_load</code></div>
|
||||
<div class="auto-btn"><span style="font-size: 2rem">↻</span><strong>重载</strong><code>btn_reload</code></div>
|
||||
<div class="auto-btn"><span style="font-size: 2rem">▶</span><strong>运行</strong><code>btn_run</code></div>
|
||||
<div class="auto-btn"><span style="font-size: 2rem">■</span><strong>停止</strong><code>btn_stop</code></div>
|
||||
<div class="auto-btn"><span style="font-size: 2rem">Ⅱ</span><strong>暂停</strong><code>tbtn_pause</code></div>
|
||||
<div class="auto-btn"><span style="font-size: 2rem">→</span><strong>单步</strong><code>btn_step</code></div>
|
||||
</div>
|
||||
<svg viewBox="0 0 760 178" aria-hidden="true">
|
||||
<defs><marker id="arrow-btn-load" markerWidth="16" markerHeight="16" refX="13" refY="8" orient="auto"><path d="M0,0 L16,8 L0,16 z" fill="#d62828"/></marker></defs>
|
||||
<path class="arrow" marker-end="url(#arrow-btn-load)" d="M305 28 C245 38 156 50 67 91"/>
|
||||
<text class="label" x="320" y="30">点击这里:btn_load</text>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="notice"><strong>如果你只看到右侧 AUTO 图标而没有这排按钮:</strong>你还没有完成 AUTO 页面切换,或者 AUTO 被急停/未上电/未 homing 状态门控。先点击上面的 AUTO 入口,等按钮组切换完成,再寻找红框位置。</div>
|
||||
<div class="notice"><strong>不要把两个按钮混淆:</strong><code>btn_load</code> 负责“打开文件选择界面”;文件界面底部的绿色对勾 <code>btn_select</code> 负责“确认当前选中的文件”。</div>
|
||||
<div class="success">当前仿真配置的共享 home switch 曾报告 <code>Cannot home while shared home switch is closed j=2</code>。这会让 AUTO 保持门控;它是仿真 HAL 状态问题,不是加载按钮消失。</div>
|
||||
</section>
|
||||
|
||||
<section id="select">
|
||||
<h2>3. 选择 G-code 程序</h2>
|
||||
<p>只有完成上一节的 <strong>AUTO → 打开文件图标</strong> 两次点击后,才会出现下面这个文件界面。进入文件页后,GMOCCAPY 会按配置中的 <code>PROGRAM_PREFIX = ../../nc_files/</code> 显示程序。当前配置解析到:</p>
|
||||
<pre>/home/mes006/linuxcncWebWASM/linuxcnc/nc_files/</pre>
|
||||
<figure class="figure">
|
||||
<div class="annotated">
|
||||
<img src="gmoccapy-load-file-page.png" alt="GMOCCAPY 文件选择页">
|
||||
<svg viewBox="0 0 1960 1500" aria-hidden="true">
|
||||
<defs><marker id="arrow-file" markerWidth="18" markerHeight="18" refX="15" refY="9" orient="auto"><path d="M0,0 L18,9 L0,18 z" fill="#d62828"/></marker></defs>
|
||||
<path class="arrow arrow-thin" marker-end="url(#arrow-file)" d="M1530 570 C1490 520 1440 475 1355 420"/>
|
||||
<text class="label" x="1500" y="640">3 选择 .ngc</text>
|
||||
<path class="arrow arrow-thin" marker-end="url(#arrow-file)" d="M1290 1260 C1390 1280 1480 1320 1530 1370"/>
|
||||
<text class="label" x="1060" y="1240">4 确认选择</text>
|
||||
</svg>
|
||||
</div>
|
||||
<figcaption>示例中可以看到 <code>3dtest.ngc</code>。底部右侧绿色对勾用于返回选中的路径;黄色弯箭头用于返回上一页。文件网格不会在 MANUAL/JOG 页面直接出现。</figcaption>
|
||||
</figure>
|
||||
<h3>界面出现后的点击顺序</h3>
|
||||
<ol class="steps">
|
||||
<li>在文件网格中单击目标文件,例如 <code>3dtest.ngc</code>;选中项会出现高亮。</li>
|
||||
<li>点击底部右侧绿色对勾(<code>btn_select</code>)。它会返回文件路径并关闭文件选择页。</li>
|
||||
<li>回到主界面后,顶部标题应显示完整路径,预览区应显示刀具轨迹。</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section id="verify">
|
||||
<h2>4. 确认程序已经加载</h2>
|
||||
<p>加载成功后,顶部标题不再是 <code>No Program loaded</code>,而会显示完整文件路径;左侧预览区会出现刀具轨迹。加载本身不会启动运动。</p>
|
||||
<figure class="figure">
|
||||
<div class="annotated">
|
||||
<img src="gmoccapy-program-loaded.png" alt="3dtest.ngc 已加载并显示刀具轨迹">
|
||||
<svg viewBox="0 0 1960 1500" aria-hidden="true">
|
||||
<defs><marker id="arrow-verify" markerWidth="18" markerHeight="18" refX="15" refY="9" orient="auto"><path d="M0,0 L18,9 L0,18 z" fill="#d62828"/></marker></defs>
|
||||
<path class="arrow arrow-thin" marker-end="url(#arrow-verify)" d="M1310 95 C1120 80 930 55 730 38"/>
|
||||
<text class="label" x="1320" y="140">5 已加载路径</text>
|
||||
<path class="arrow arrow-thin" marker-end="url(#arrow-verify)" d="M665 850 C545 760 420 650 280 560"/>
|
||||
<text class="label" x="680" y="900">6 刀具轨迹预览</text>
|
||||
</svg>
|
||||
</div>
|
||||
<figcaption>本次测试加载的文件是 <code>3dtest.ngc</code>;LinuxCNC 状态接口返回该路径,解释器保持空闲。</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<section id="trouble">
|
||||
<h2>常见问题</h2>
|
||||
<div class="two-col">
|
||||
<div class="fact"><strong>看不到加载按钮</strong>按“关闭首次提示 → 右侧第三个 AUTO 图标 → AUTO 按钮组中的打开文件图标”顺序操作。文件网格本身不是入口,它是点击 <code>btn_load</code> 之后才出现的。</div>
|
||||
<div class="fact"><strong>AUTO 图标是灰色</strong>检查急停复位、机器上电和 homing。GMOCCAPY 会在状态不满足时禁用模式切换。</div>
|
||||
<div class="fact"><strong>加载后没有运动</strong>这是正常的。加载只生成程序和预览;要执行必须点击 Run 或按 <code>R</code>。</div>
|
||||
<div class="fact"><strong>启动日志提示 POSIX non-realtime</strong>当前环境没有实时内核。可用于仿真界面和加载检查,但不能作为实时硬件性能证明。</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="evidence">
|
||||
<h2>源码、配置与证据</h2>
|
||||
<table>
|
||||
<thead><tr><th>项目</th><th>位置</th><th>用途</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>AUTO 入口</td><td><code>rbt_auto</code> / <code>on_rbt_auto_pressed</code></td><td>右侧第三个模式按钮;先进入 AUTO,加载按钮才可见。</td></tr>
|
||||
<tr><td>AUTO 按钮组</td><td><code>hbtb_auto</code></td><td>GMOCCAPY Glade 中承载打开、重载、运行、停止、暂停和单步按钮。</td></tr>
|
||||
<tr><td>加载按钮</td><td><code>gmoccapy.py:5046</code>、<code>btn_load</code></td><td>切换到文件选择页。</td></tr>
|
||||
<tr><td>文件确认</td><td><code>gmoccapy.py:5116</code>、<code>btn_select</code></td><td>将选中的文件交给 <code>hal_action_open.load_file(path)</code>。</td></tr>
|
||||
<tr><td>程序目录</td><td><code>configs/sim/gmoccapy/gmoccapy.ini</code></td><td><code>PROGRAM_PREFIX = ../../nc_files/</code>。</td></tr>
|
||||
<tr><td>逐文件分析</td><td><a href="gmoccapy-file-inventory.md">gmoccapy-file-inventory.md</a></td><td>1,233 个配置、实现、资源、图标、翻译和文档文件的哈希清单。</td></tr>
|
||||
<tr><td>完整分析</td><td><a href="gmoccapy-analysis.md">gmoccapy-analysis.md</a></td><td>本次提示、界面、加载测试和限制的详细记录。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>本页是静态 HTML,不依赖网络。复制整个 <code>doc-g</code> 目录后,直接用浏览器打开本文件即可。</p>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
<div class="footer">GMOCCAPY 仿真使用说明 · 证据和后续文档统一保存在 <code>./doc-g</code></div>
|
||||
</body>
|
||||
</html>
|
||||
118
doc-g/generate_inventory.py
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a deterministic inventory for the GMOCCAPY source and case assets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUT = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def classify(rel: str) -> str:
|
||||
path = Path(rel)
|
||||
if "/configs/sim/gmoccapy/" in rel:
|
||||
if path.suffix == ".ini":
|
||||
return "simulation_ini"
|
||||
if path.suffix in {".hal", ".tbl", ".para", ".var", ".pref"}:
|
||||
return "simulation_runtime_data"
|
||||
if path.suffix in {".ngc", ".py", ".ui", ".glade", ".svg", ".txt", ".README"}:
|
||||
return "simulation_resource"
|
||||
return "simulation_asset"
|
||||
if "/src/emc/usr_intf/gmoccapy/" in rel or "/lib/python/gmoccapy/" in rel:
|
||||
return "implementation"
|
||||
if "/share/gmoccapy/" in rel:
|
||||
if "icons/" in rel:
|
||||
return "icon_asset"
|
||||
return "installed_ui_asset"
|
||||
if "/src/po/gmoccapy/" in rel:
|
||||
return "translation"
|
||||
if "/docs/src/gui/images/gmoccapy_" in rel or "/docs/src/user/images/gmoccapy_" in rel:
|
||||
return "documentation_asset"
|
||||
if "/docs/src/gui/gmoccapy" in rel or "/docs/man/man1/gmoccapy" in rel:
|
||||
return "documentation"
|
||||
return "cross_reference"
|
||||
|
||||
|
||||
def collect(paths: list[Path]) -> list[dict[str, object]]:
|
||||
files: list[Path] = []
|
||||
for base in paths:
|
||||
if base.is_file():
|
||||
files.append(base)
|
||||
elif base.is_dir():
|
||||
files.extend(p for p in base.rglob("*") if p.is_file())
|
||||
rows = []
|
||||
for path in sorted(set(files)):
|
||||
rel = path.relative_to(ROOT).as_posix()
|
||||
rows.append(
|
||||
{
|
||||
"path": rel,
|
||||
"role": classify(rel),
|
||||
"bytes": path.stat().st_size,
|
||||
"sha256": sha256(path),
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def main() -> None:
|
||||
rows = collect(
|
||||
[
|
||||
ROOT / "linuxcnc/configs/sim/gmoccapy",
|
||||
ROOT / "linuxcnc/src/emc/usr_intf/gmoccapy",
|
||||
ROOT / "linuxcnc/lib/python/gmoccapy",
|
||||
ROOT / "linuxcnc/share/gmoccapy",
|
||||
ROOT / "linuxcnc/src/po/gmoccapy",
|
||||
ROOT / "linuxcnc/docs/src/gui/gmoccapy.adoc",
|
||||
ROOT / "linuxcnc/docs/man/man1/gmoccapy.1",
|
||||
*sorted((ROOT / "linuxcnc/docs/src/gui/images").glob("gmoccapy_*")),
|
||||
*sorted((ROOT / "linuxcnc/docs/src/user/images").glob("gmoccapy_*")),
|
||||
]
|
||||
)
|
||||
summary: dict[str, int] = {}
|
||||
for row in rows:
|
||||
role = str(row["role"])
|
||||
summary[role] = summary.get(role, 0) + 1
|
||||
payload = {
|
||||
"schema": "gmoccapy-file-inventory.v1",
|
||||
"scope": [
|
||||
"linuxcnc/configs/sim/gmoccapy/**",
|
||||
"linuxcnc/src/emc/usr_intf/gmoccapy/**",
|
||||
"linuxcnc/lib/python/gmoccapy/**",
|
||||
"linuxcnc/share/gmoccapy/**",
|
||||
"linuxcnc/src/po/gmoccapy/**",
|
||||
"linuxcnc/docs/src/gui/gmoccapy.adoc",
|
||||
"linuxcnc/docs/man/man1/gmoccapy.1",
|
||||
"linuxcnc/docs/src/gui/images/gmoccapy_*",
|
||||
"linuxcnc/docs/src/user/images/gmoccapy_*",
|
||||
],
|
||||
"file_count": len(rows),
|
||||
"role_counts": dict(sorted(summary.items())),
|
||||
"files": rows,
|
||||
}
|
||||
(OUT / "gmoccapy-file-inventory.json").write_text(
|
||||
json.dumps(payload, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
lines = [
|
||||
"# GMOCCAPY 文件清单",
|
||||
"",
|
||||
f"清单范围内共 `{len(rows)}` 个文件;每项包含相对路径、角色、字节数和 SHA-256。",
|
||||
"",
|
||||
"| 角色 | 数量 |",
|
||||
"|---|---:|",
|
||||
]
|
||||
lines.extend(f"| `{role}` | {count} |" for role, count in sorted(summary.items()))
|
||||
lines.extend(["", "完整逐文件数据见 [`gmoccapy-file-inventory.json`](gmoccapy-file-inventory.json)。", ""])
|
||||
(OUT / "gmoccapy-file-inventory.md").write_text("\n".join(lines), encoding="utf-8")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
doc-g/gmoccapy-3dtest-loaded-through-ui.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
doc-g/gmoccapy-after-popup-close.png
Normal file
|
After Width: | Height: | Size: 294 KiB |
170
doc-g/gmoccapy-analysis.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# GMOCCAPY 分析与加载测试
|
||||
|
||||
更新时间:2026-08-25
|
||||
|
||||
## 结论
|
||||
|
||||
“没有看到加载程序按钮”不是按钮缺失,而是首次启动提示和 GMOCCAPY 的模式门控共同造成的视觉结果:
|
||||
|
||||
1. 首次启动会弹出模态的 `Important change(s)` 窗口。该窗口覆盖主界面并阻止底层点击;用户截图中的右侧 `X` 是关闭按钮,底部复选框是 `Don't show this again`。
|
||||
2. 加载按钮只属于 AUTO 页面按钮组,不属于 MANUAL/JOG 页面。源码将它声明为 `btn_load`,提示为 `Load a new program`,图标为 `img_open`。
|
||||
3. AUTO 模式还受机器状态和 homing 门控。`gmoccapy.py` 在未满足状态时会禁用 `rbt_auto`,并提示 `It is not possible to change to Auto Mode at the moment`。
|
||||
4. 关闭首次提示、进入 AUTO 后,点击文件夹/打开文件图标会进入文件选择页。当前 `gmoccapy.ini` 的 `PROGRAM_PREFIX = ../../nc_files/` 解析到 `linuxcnc/nc_files`。
|
||||
|
||||
## 用户截图分析
|
||||
|
||||

|
||||
|
||||
用户提供的图片尺寸为 `697 x 837`,内容是 GMOCCAPY 3.5.x 的版本变更提示:
|
||||
|
||||
- LinuxCNC 2.10/GMOCCAPY 3.5.0 不再在换刀后自动保留 G43;需要通过 REMAP 实现自动重新激活。
|
||||
- 3.5.1 的工具表和偏置页默认使用计算器输入,可在设置或按钮中改变。
|
||||
- MDI 和 G-code 编辑模式新增调用计算器的按钮。
|
||||
- `release notes` 是橙色链接;右下方复选框用于永久隐藏该提示。
|
||||
|
||||
这张提示不是文件选择器,也不是加载失败信息。应先关闭它,再操作主界面。
|
||||
|
||||
### 新截图的源码逐项对照
|
||||
|
||||

|
||||
|
||||
这张截图与前一张内容一致,尺寸为 `697 x 837`,SHA-256 为 `df7322c30bcb6af131591a04b11cad24e7b6d186beaff83059fcfd8ffbae2d1b`。截图中的每一块文字都能在当前系统安装的 `/usr/bin/gmoccapy` 中找到:
|
||||
|
||||
| 截图区域 | 源码证据 | 含义 |
|
||||
|---|---|---|
|
||||
| `Important change(s)` 标题 | `/usr/bin/gmoccapy:557-560` | `_startup_message()` 建立启动提示标题;注释要求新增消息只能追加,不能删除旧消息,否则隐藏索引会错位。 |
|
||||
| 3.5.0 G43 提示 | `/usr/bin/gmoccapy:561-563` | GMOCCAPY 3.5.0 不再承诺换刀后自动保留 G43;想自动恢复必须使用 REMAP,示例在 GMOCCAPY 仿真配置中。 |
|
||||
| 3.5.1 计算器提示 | `/usr/bin/gmoccapy:564-565` | 工具表和偏置页默认启用计算器输入;设置或页面按钮可以关闭;MDI 和 G-code 编辑页也增加了计算器按钮。 |
|
||||
| `release notes` 链接 | `/usr/bin/gmoccapy:570-573` | 以 Pango markup 拼接 LinuxCNC 官方 release notes URL,并通过通知组件显示。点击它会打开文档,不会关闭提示。 |
|
||||
| `Don't show this again` | `notification.py:151-155` | `show_checkbox=True` 时创建 GTK `CheckButton`;它不是普通说明文字,而是真正影响后续启动行为的复选框。 |
|
||||
| 右侧 `X` | `notification.py:156-164` | 创建名为 `notification_close` 的 48x48 关闭按钮;点击后删除该消息,并把复选框状态发回 GMOCCAPY。 |
|
||||
|
||||
### 弹窗什么时候出现
|
||||
|
||||
系统运行时在 GUI 初始化和周期任务准备完成后,于 `/usr/bin/gmoccapy:519` 调用 `_startup_message()`。它读取偏好项 `hide_startup_messsage`(注意源码中确实有三个 `s` 的拼写):
|
||||
|
||||
```python
|
||||
hide_message = self.prefs.getpref("hide_startup_messsage", 0, int)
|
||||
if hide_message < len(messages):
|
||||
...
|
||||
self.notification.add_message(message, INFO_ICON, show_checkbox=True)
|
||||
```
|
||||
|
||||
当前消息数组有两项,因此默认值 `0` 会显示两项。关闭时如果复选框被勾选,`/usr/bin/gmoccapy:6241-6242` 将 `self.num`(当前为 `2`)写回 `hide_startup_messsage`。下次启动判断 `2 < 2` 为假,提示不再出现。普通点击 `X` 不勾选复选框时,只删除本次通知,下次仍会显示。
|
||||
|
||||
### 版本边界:截图运行时与仓库锁定源不同
|
||||
|
||||
这是本次分析中最重要的来源边界:
|
||||
|
||||
- 截图对应的实际运行程序是 `/usr/bin/gmoccapy`,其中 `_RELEASE = "3.5.2"`,并包含 `_startup_message()`、3.5.0/3.5.1 文本和隐藏偏好逻辑。
|
||||
- 仓库锁定源是 `linuxcnc/VERSION = 2.10.0~pre0`;`linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py:79` 的版本为 `3.4.3`,该文件没有 `_startup_message()` 实现,也没有截图中的 3.5.0/3.5.1 文本。
|
||||
- 本地启动日志曾显示 `LinuxCNC 2.10.0~pre1` 和 `gmoccapy 3.5.2`,与截图完全一致。因此这张截图是系统安装包的运行时证据,不应反向宣称锁定仓库 2.10.0~pre0/3.4.3 源码已经包含该功能。
|
||||
|
||||
已记录的运行时文件哈希:
|
||||
|
||||
```text
|
||||
/usr/bin/gmoccapy 11e7aee9216d24e4cc41018306f2541845bca9a9d6e16c38580f3ede9cca8d4d
|
||||
/usr/share/gmoccapy/gmoccapy.glade 5fdc1557b02a716d30414fe739614fcd7c1d14cbe2e0a4c202c567a60b460adb
|
||||
/usr/lib/python3/dist-packages/gmoccapy/notification.py
|
||||
e0317fea738745f480c79d1f690bc52e88cc7f81bb4d14fa09c8c7196afb4e2f
|
||||
```
|
||||
|
||||
### 用户应如何处理
|
||||
|
||||
1. 只想本次继续操作:点击右侧 `X`,不要勾选复选框。
|
||||
2. 不想以后重复看到:先勾选 `Don't show this again`,再点击 `X`。
|
||||
3. 想查看变更依据:点击 `release notes`,但这需要浏览器/网络或本地文档可用。
|
||||
4. 使用 G43 的程序:不要仅依赖界面自动行为;在 AUTO 程序中按机床配置和 REMAP 约定明确处理刀具长度补偿,并在正式加工前验证工具表和偏置。
|
||||
|
||||
## 实际界面证据
|
||||
|
||||
### 未加载状态
|
||||
|
||||

|
||||
|
||||
主界面顶部显示 `No Program loaded`。右侧竖向按钮中第三个为 AUTO 模式图标;在 MANUAL 页面时,AUTO 页面里的 `Load a new program` 不会显示在当前按钮组。
|
||||
|
||||
### 文件选择页
|
||||
|
||||

|
||||
|
||||
这张截图是实际运行 GMOCCAPY 后进入的文件选择页。页面显示 `gcmc_lib`、`macros`、`ngcgui_lib` 等目录以及 `3D_Chips.ngc`、`3dtest.ngc` 等程序。底部右侧绿色对勾是确认选择,右下黄色回退箭头是返回。
|
||||
|
||||
进入这张页面的完整点击链是:
|
||||
|
||||
1. 关闭首次 `Important change(s)` 模态提示。
|
||||
2. 在主界面右侧点击第三个模式图标,进入 AUTO;源码控件为 `rbt_auto`。
|
||||
3. 等待 AUTO 按钮组出现,在其中点击打开文件图标;源码控件为 `btn_load`,tooltip 为 `Load a new program`。
|
||||
4. `on_btn_load_clicked()` 将按钮页切换到文件选择页;此时才会看到本截图中的文件网格。
|
||||
5. 选中 `.ngc` 文件后点击底部绿色对勾,源码控件为 `btn_select`。
|
||||
|
||||
因此,文件网格不是主界面的固定按钮,也不是 MDI 入口;在 MANUAL/JOG 页面直接寻找它不会找到。
|
||||
|
||||
### 加载后的程序
|
||||
|
||||

|
||||
|
||||
通过 LinuxCNC 的 `command.program_open()` 加载:
|
||||
|
||||
```text
|
||||
/home/mes006/linuxcncWebWASM/linuxcnc/nc_files/3dtest.ngc
|
||||
```
|
||||
|
||||
加载成功的证据是:
|
||||
|
||||
- 顶部由 `No Program loaded` 变成完整程序路径;
|
||||
- 预览区出现 G-code 刀具轨迹;
|
||||
- `linuxcnc.stat().file` 返回上述路径;
|
||||
- `interp_state` 保持空闲,加载本身不会自动运行程序。
|
||||
|
||||
## 源码与界面映射
|
||||
|
||||
| 层 | 文件/符号 | 结论 |
|
||||
|---|---|---|
|
||||
| 主逻辑 | `linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py` | `on_btn_load_clicked()` 切到文件选择页;`on_IconFileSelection1_selected()` 调用 `hal_action_open.load_file(path)`。 |
|
||||
| 加载按钮 | `btn_load` / `img_open` | tooltip 是 `Load a new program`,只在 AUTO 按钮页 `hbtb_auto`。 |
|
||||
| 运行按钮 | `btn_run` / `img_run` | tooltip 是 `Run the loaded program`;加载后仍需手动运行。 |
|
||||
| 文件确认 | `btn_select` / `img_select` | tooltip 是 `select the highlighted file and return the path`。 |
|
||||
| 返回 | `btn_back_file_load` / `img_back_file_load` | 关闭文件选择页但不返回路径。 |
|
||||
| 文件资源 | `linuxcnc/lib/python/gladevcp/iconview.py` 及 GMOCCAPY 初始化 | 读取 `PROGRAM_PREFIX`,按配置扩展名展示文件和目录。 |
|
||||
| 首次提示 | GMOCCAPY preferences/release-notes 逻辑 | 模态提示优先于主界面,必须先关闭。 |
|
||||
|
||||
## 配置与资源分析
|
||||
|
||||
默认配置为 `linuxcnc/configs/sim/gmoccapy/gmoccapy.ini`:
|
||||
|
||||
- `[DISPLAY] DISPLAY = gmoccapy`:选择 GMOCCAPY GUI。
|
||||
- `[DISPLAY] PROGRAM_PREFIX = ../../nc_files/`:从配置目录解析到 `linuxcnc/nc_files`。
|
||||
- `[RS274NGC] PARAMETER_FILE = sim.var`:仿真参数文件。
|
||||
- `[RS274NGC] SUBROUTINE_PATH = macros`:宏和子程序搜索路径。
|
||||
- `[EMCIO] TOOL_TABLE = tool.tbl`:工具表。
|
||||
- `[HAL]` 加载 `core_sim.hal`、`spindle_sim.hal`、`simulated_home.hal`,并在 GUI 启动后加载 `gmoccapy_postgui.hal`。
|
||||
|
||||
`configs/sim/gmoccapy` 不是单一案例,而是一组配置族,包含标准三轴、四轴、XYZAB/XYZAC/XYZCW、多轴、侧面板、消息、工具传感器、用户页、等离子和车床配置。完整逐文件路径与哈希见 [文件清单](gmoccapy-file-inventory.md)。
|
||||
|
||||
## 运行环境与限制
|
||||
|
||||
- 启动入口:[`../run-gmoccapy.sh`](../run-gmoccapy.sh),默认执行 `gmoccapy.ini`。
|
||||
- 实际 GUI 进程:`/usr/bin/python3 /usr/bin/gmoccapy -ini .../gmoccapy.ini`。
|
||||
- 当前环境没有实时内核,LinuxCNC 日志明确回退到 POSIX non-realtime;这不影响本次“加载和预览”验证,但不等同于实时硬件运行验证。
|
||||
- `simulated_home.hal` 的共享 home switch 在本次手动尝试中报告 `Cannot home while shared home switch is closed j=2`,因此 AUTO 模式按钮可能保持禁用。加载程序本身仍可通过 GUI 文件页/API 验证。
|
||||
- 加载和运行是两个动作:`program_open` 只读入程序并生成预览;需要 `AUTO` 页面中的 Run 按钮或 `R` 快捷键才会执行。
|
||||
|
||||
## 推荐操作顺序
|
||||
|
||||
1. 启动 `./run-gmoccapy.sh`。
|
||||
2. 关闭首次 `Important change(s)` 提示;可勾选 `Don't show this again`。
|
||||
3. 确认急停复位、机器上电,并按配置完成 homing;否则 AUTO 可能被门控。
|
||||
4. 点击右侧 AUTO 图标,进入 AUTO 按钮页。
|
||||
5. 点击带打开文件/文件夹图标的 `Load a new program`。
|
||||
6. 选择 `linuxcnc/nc_files` 下的 `.ngc` 文件,点击底部绿色对勾。
|
||||
7. 检查顶部路径和预览轨迹,再使用 Run;不要把加载误认为已经执行。
|
||||
|
||||
## 证据文件
|
||||
|
||||
- `gmoccapy-important-changes.png`:用户原始提示截图。
|
||||
- `gmoccapy-before-load.png`:未加载程序。
|
||||
- `gmoccapy-after-popup-close.png`:关闭提示后的主界面。
|
||||
- `gmoccapy-load-file-page.png`:实际文件选择页。
|
||||
- `gmoccapy-program-loaded.png`:加载后的程序和轨迹。
|
||||
- `gmoccapy-file-inventory.json`:逐文件 SHA-256 清单。
|
||||
BIN
doc-g/gmoccapy-auto-active-icon.png
Normal file
|
After Width: | Height: | Size: 738 B |
BIN
doc-g/gmoccapy-auto-api.png
Normal file
|
After Width: | Height: | Size: 278 KiB |
BIN
doc-g/gmoccapy-auto-api2.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
doc-g/gmoccapy-auto-click2.png
Normal file
|
After Width: | Height: | Size: 282 KiB |
BIN
doc-g/gmoccapy-auto-click3.png
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
doc-g/gmoccapy-auto-focused.png
Normal file
|
After Width: | Height: | Size: 296 KiB |
BIN
doc-g/gmoccapy-auto-homed.png
Normal file
|
After Width: | Height: | Size: 296 KiB |
BIN
doc-g/gmoccapy-auto-mode.png
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
doc-g/gmoccapy-auto-success-candidate.png
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
doc-g/gmoccapy-auto.png
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
doc-g/gmoccapy-before-load.png
Normal file
|
After Width: | Height: | Size: 282 KiB |
323
doc-g/gmoccapy-control-panel-data-control-flow.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# GMOCCAPY 控制面板功能、数据流与控制流
|
||||
|
||||
更新时间:2026-08-25
|
||||
|
||||
本文是后续分析 GMOCCAPY 控制面板的总入口。它把普通三轴 `gmoccapy.ini` 和五轴 XYZAC RTCP 案例放在同一套模型中,区分 UI 控件、GMOCCAPY Python handler、LinuxCNC command/status、TASK/motion、HAL 和 Vismach 的责任边界。
|
||||
|
||||
## 1. 范围与版本边界
|
||||
|
||||
| 项目 | 当前工作区/运行时 |
|
||||
|---|---|
|
||||
| 仓库源码 | `linuxcnc/VERSION = 2.10.0~pre0` |
|
||||
| 仓库 GMOCCAPY 源码 | `linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py`,`_RELEASE = 3.4.3` |
|
||||
| 实际系统 GUI | `/usr/bin/gmoccapy`,运行时版本 `3.5.2`,系统 LinuxCNC `2.10.0~pre1` |
|
||||
| 普通面板入口 | [`run-gmoccapy.sh`](../run-gmoccapy.sh),默认 `configs/sim/gmoccapy/gmoccapy.ini` |
|
||||
| 五轴 RTCP 入口 | [`run-5axis-rtcp.sh`](../run-5axis-rtcp.sh),默认 XYZAC TRT switchkins |
|
||||
| 发布/仿真边界 | POSIX non-realtime 仿真;不等同于实时硬件验证 |
|
||||
|
||||
截图中的 `Important change(s)` 弹窗来自系统 GMOCCAPY 3.5.2,不存在于仓库 3.4.3 源文件中;两套来源不能混为一个源码版本。详细证据见 [GMOCCAPY 分析报告](gmoccapy-analysis.md)。
|
||||
|
||||
## 2. 总体架构
|
||||
|
||||
```text
|
||||
快捷方式 / linuxcnc -r CASE.ini
|
||||
│
|
||||
▼
|
||||
LinuxCNC 启动器与 INI 解析
|
||||
│
|
||||
├── linuxcncsvr / NML 状态与命令通道
|
||||
├── milltask / RS274NGC interpreter / task mode
|
||||
├── motmod + kinematics / fixed servo cycle
|
||||
├── HAL files / HALUI / simulated devices
|
||||
└── DISPLAY=gmoccapy 或 DISPLAY=axis
|
||||
│
|
||||
▼
|
||||
GMOCCAPY Gtk/Glade 控制面板
|
||||
│
|
||||
┌─────────┴─────────┐
|
||||
│ │
|
||||
用户点击/键盘/HAL linuxcnc.stat/HAL status
|
||||
│ ▲
|
||||
▼ │
|
||||
Python handler ── command ─┴── TASK/motion/HAL feedback
|
||||
│
|
||||
├── 模式/按钮页切换
|
||||
├── 程序加载/预览
|
||||
├── AUTO 运行/暂停/停止/单步
|
||||
├── MDI 命令
|
||||
└── homing、tool、offset、spindle、coolant
|
||||
```
|
||||
|
||||
GMOCCAPY 不是运动学计算器。它负责把操作员动作转换为 LinuxCNC command,并把 LinuxCNC status/HAL status 投影回界面;真正的解释器、轨迹规划、逆解/正解和关节命令由 LinuxCNC runtime 完成。
|
||||
|
||||
## 3. 启动控制流
|
||||
|
||||
### 3.1 快捷方式层
|
||||
|
||||
`run-gmoccapy.sh` 和 `run-5axis-rtcp.sh` 都遵循相同模式:
|
||||
|
||||
1. 计算仓库绝对路径,避免从任意工作目录启动时相对路径漂移。
|
||||
2. 校验 INI、演示程序、LinuxCNC launcher 是否存在。
|
||||
3. 进入 LinuxCNC 根目录。
|
||||
4. 执行 `/usr/bin/linuxcnc -r CASE.ini`,保留终端诊断。
|
||||
|
||||
五轴快捷方式额外检查 RTCP 子程序中确实存在 `M428`,并支持 `xyzac-trt.ini` 与 `xyzbc-trt.ini` 两个变体。
|
||||
|
||||
### 3.2 INI 到进程
|
||||
|
||||
以普通 GMOCCAPY 为例:
|
||||
|
||||
```text
|
||||
[DISPLAY] DISPLAY=gmoccapy
|
||||
[TASK] TASK=milltask
|
||||
[HAL] core_sim.hal + spindle_sim.hal + simulated_home.hal
|
||||
[EMCIO] TOOL_TABLE=tool.tbl
|
||||
[RS274NGC] PARAMETER_FILE=sim.var, SUBROUTINE_PATH=macros
|
||||
[KINS] trivkins coordinates=xyz
|
||||
```
|
||||
|
||||
启动器随后创建 `linuxcncsvr`、`milltask`、`halui` 和 GMOCCAPY。GMOCCAPY 进程通过 `-ini` 得到同一个 INI 路径,不能自行替代 TASK/motion/HAL。
|
||||
|
||||
### 3.3 GMOCCAPY 初始化顺序
|
||||
|
||||
仓库源码 `gmoccapy.py` 的主初始化顺序为:
|
||||
|
||||
1. 解析 `-user_mode`、`-logo` 参数(约 `243-270` 行)。
|
||||
2. 读取 INI(`_get_ini_data()`,约 `272-275` 行)。
|
||||
3. 读取偏好文件(`_get_pref_data()`)。
|
||||
4. 动态创建 DRO、home、touch-off、jog、macro 等控件(约 `280-305` 行)。
|
||||
5. 建立 GMOCCAPY HAL pins(约 `307` 行)。
|
||||
6. 连接 Glade signals(约 `315-320` 行)。
|
||||
7. 显示主窗口(约 `324-325` 行)。
|
||||
8. 初始化主题、G-code preview、kinematics view、offset/tool editor、keyboard 和文件选择器(约 `327-342` 行)。
|
||||
|
||||
系统 3.5.2 运行时在 GUI 初始化后额外调用 `_startup_message()`,生成版本提示弹窗;它属于安装包新增功能,不属于仓库 3.4.3 初始化链。
|
||||
|
||||
## 4. 控制面板模式和按钮门控
|
||||
|
||||
### 4.1 模式按钮
|
||||
|
||||
Glade 中的主要模式控件:
|
||||
|
||||
| 控件 | 作用 | handler/状态 |
|
||||
|---|---|---|
|
||||
| `rbt_manual` | MANUAL/JOG | `on_rbt_manual_pressed()` 发出 `MODE_MANUAL` |
|
||||
| `rbt_mdi` | MDI | `on_rbt_mdi_pressed()` 发出 `MODE_MDI` |
|
||||
| `rbt_auto` | AUTO | `on_rbt_auto_pressed()` 发出 `MODE_AUTO` |
|
||||
| `tbtn_setup` | 设置页 | 进入设置前强制 MANUAL,并禁用模式按钮 |
|
||||
| `tbtn_user_tabs` | 用户页 | 切换主 notebook 页面 |
|
||||
|
||||
`on_hal_status_mode_auto()` 在收到 HAL status 后将主页面切回 page 0,把按钮 notebook 切到 `_BB_AUTO`,并将 jog notebook 切到 AUTO 页面(约 `2804-2824` 行)。因此“点击 AUTO 图标”与“看到 AUTO 按钮组”是同一个状态转换的两个可观察结果。
|
||||
|
||||
### 4.2 急停、上电、homing 门控
|
||||
|
||||
`on_tbtn_setup_toggled()` 和 HAL status handler 根据以下条件设置按钮敏感度:
|
||||
|
||||
```text
|
||||
ESTOP → manual/mdi/auto 均禁用
|
||||
ON + 未 homed → 只允许 manual
|
||||
ON + all_homed 或 NO_FORCE_HOMING → manual/mdi/auto 全部可用
|
||||
```
|
||||
|
||||
对应仓库源码约为 `3793-3808` 行。未满足条件时,点击 AUTO 会被 `on_hal_status_mode_auto()` 拒绝,显示 `It is not possible to change to Auto Mode at the moment`,并回到 MANUAL。
|
||||
|
||||
这解释了为什么控制面板可能“没有加载按钮”:加载按钮不在 MANUAL 页面,而且 AUTO 页面本身可能因状态门控没有激活。
|
||||
|
||||
## 5. G-code 加载数据流
|
||||
|
||||
### 5.1 控件链
|
||||
|
||||
```text
|
||||
rbt_auto
|
||||
│ on_hal_status_mode_auto()
|
||||
▼
|
||||
hbtb_auto / _BB_AUTO
|
||||
│
|
||||
│ btn_load(图标 img_open,tooltip=Load a new program)
|
||||
│ on_btn_load_clicked()
|
||||
▼
|
||||
_BB_LOAD_FILE + IconFileSelection1
|
||||
│
|
||||
│ 用户选中路径
|
||||
│ btn_select / IconFileSelection1_selected
|
||||
▼
|
||||
hal_action_open.load_file(path)
|
||||
▼
|
||||
LinuxCNC task/interpreter + preview/status
|
||||
▼
|
||||
ntb_preview page 0 + ntb_button _BB_AUTO + stat.file
|
||||
```
|
||||
|
||||
### 5.2 打开文件界面
|
||||
|
||||
`on_btn_load_clicked()`(`gmoccapy.py:5046-5078`)执行:
|
||||
|
||||
1. `ntb_button.set_current_page(_BB_LOAD_FILE)`。
|
||||
2. `ntb_preview.set_current_page(3)`。
|
||||
3. 打开 full-size preview。
|
||||
4. `_show_iconview_tab(True)`。
|
||||
5. 刷新 `IconFileSelection1` 文件列表。
|
||||
6. 暂时禁用 MANUAL、MDI 和设置按钮,避免文件选择过程中切换模式。
|
||||
|
||||
文件选择器在 `_init_IconFileSelection()` 和 `_init_file_to_load()` 中读取 `PROGRAM_PREFIX` 与 `[FILTER] PROGRAM_EXTENSION`。普通案例 `PROGRAM_PREFIX = ../../nc_files/` 最终指向仓库的 `linuxcnc/nc_files`。
|
||||
|
||||
### 5.3 确认文件
|
||||
|
||||
`on_IconFileSelection1_selected()`(`gmoccapy.py:5114-5125`)执行:
|
||||
|
||||
1. 将选中的 `path` 交给 `hal_action_open.load_file(path)`。
|
||||
2. 恢复 preview page 0。
|
||||
3. 关闭 full-size preview 和 icon view。
|
||||
4. 把按钮 notebook 切回 `_BB_AUTO`。
|
||||
5. 重新启用 MANUAL、MDI 和设置按钮。
|
||||
|
||||
这一步是“加载程序”,不是“运行程序”。运行还需要 `btn_run`/`AUTO_RUN`;源码键盘 handler 将 `R` 映射为运行、`P` 映射为暂停、`S` 映射为恢复。
|
||||
|
||||
### 5.4 启动时自动加载
|
||||
|
||||
`on_window1_show()` 会读取偏好项 `open_file`(约 `2920-2924` 行)。如果用户在设置页指定“启动时加载文件”,GMOCCAPY 会在窗口显示后直接调用 `hal_action_open.load_file(file)`。这条路径绕过手动点击文件选择器,但仍通过同一个 LinuxCNC load action。
|
||||
|
||||
## 6. AUTO 运行控制流
|
||||
|
||||
```text
|
||||
btn_run / R
|
||||
▼
|
||||
linuxcnc.command().auto(AUTO_RUN, line)
|
||||
▼
|
||||
milltask / RS274NGC interpreter
|
||||
▼
|
||||
TP queue → motion controller → kinematics → joint commands
|
||||
▼
|
||||
HAL simulated device / spindle / coolant / tool change
|
||||
▼
|
||||
linuxcnc.stat + hal_status
|
||||
▼
|
||||
GMOCCAPY DRO、progress、preview、button sensitivity
|
||||
```
|
||||
|
||||
相关按钮属于 `hbtb_auto`:`btn_load`、`btn_reload`、`btn_run`、`btn_stop`、`tbtn_pause`、`btn_step`、`btn_from_line`、`tbtn_optional_blocks`、`btn_edit`。Glade 负责控件和 signal 绑定,Python handler 负责调用 LinuxCNC command;运动本身不在 GTK handler 中实现。
|
||||
|
||||
## 7. MDI、Jog、Home、Tool 和 Offset 数据流
|
||||
|
||||
### MDI
|
||||
|
||||
`rbt_mdi` 将 task mode 设为 `MODE_MDI`。MDI 输入提交后进入 LinuxCNC interpreter;G-code 解析错误通过 LinuxCNC error channel 返回,再由 GMOCCAPY notification/error handler 显示。
|
||||
|
||||
### Jog
|
||||
|
||||
JOG 按钮由 `_make_jog_button()` 动态生成;轴/关节列表来自 INI 的 `COORDINATES`、kinematics 类型和 joint-axis mapping。Jog 命令仍经 LinuxCNC command 通道,不直接改变 DRO;DRO 的真值来自 status feedback。
|
||||
|
||||
### Home
|
||||
|
||||
Home 按钮最终调用 `command.home(joint_or_axis)`。仿真 HAL 的 `sim_home_switch` 或自定义 home HAL 决定 home switch 状态;`all_homed` HAL status 决定 MDI/AUTO 是否重新开放。
|
||||
|
||||
### Tool/Offset
|
||||
|
||||
工具表来自 `[EMCIO] TOOL_TABLE`,offset page 和 tool editor 通过 LinuxCNC tool data/action 更新。G43/G49/G43.1 的解释器语义由 RS274NGC 负责;GMOCCAPY 只显示和提交工具/offset 操作。系统 3.5.2 的启动提示明确提醒:AUTO 中换刀后的 G43 不能假定由界面自动恢复,应由程序/REMAP 明确处理。
|
||||
|
||||
## 8. 五轴 XYZAC RTCP 控制流
|
||||
|
||||
### 8.1 配置层
|
||||
|
||||
五轴案例 `xyzac-trt.ini` 的关键字段:
|
||||
|
||||
```text
|
||||
[DISPLAY] OPEN_FILE = ./demos/xyzac_switchkins.ngc
|
||||
[RS274NGC] SUBROUTINE_PATH = ./remap_subs
|
||||
[RS274NGC] REMAP = M428 ... ngc=428remap
|
||||
[RS274NGC] REMAP = M429 ... ngc=429remap
|
||||
[RS274NGC] REMAP = M430 ... ngc=430remap
|
||||
[KINS] KINEMATICS = xyzac-trt-kins sparm=identityfirst
|
||||
[TRAJ] COORDINATES = XYZAC
|
||||
[HAL] HALCMD = net ... motion.switchkins-type
|
||||
[HAL] HALCMD = loadusr -W xyzac-trt-gui
|
||||
```
|
||||
|
||||
### 8.2 switchkins 状态
|
||||
|
||||
`xyzac-trt-kins.c` 将 `sparm=identityfirst` 定义为:
|
||||
|
||||
```text
|
||||
switchkins-type 0 → identity kinematics
|
||||
switchkins-type 1 → xyzac TRT kinematics
|
||||
switchkins-type 2 → userk kinematics
|
||||
```
|
||||
|
||||
`trtfuncs.c` 为 XYZAC/XYZBC 实现 forward/inverse mapping,并声明 `tool-offset`、旋转中心和轴 offset HAL pins。XYZAC 的五个关节是 `X Y Z A C`;A 是倾斜台,C 是水平旋转台。
|
||||
|
||||
### 8.3 M428/M429 控制链
|
||||
|
||||
```text
|
||||
G-code M428
|
||||
▼
|
||||
428remap.ngc
|
||||
├── M68 E3 Q1 写 motion.analog-out-03 = 1
|
||||
├── M66 E0 L0 等待同步
|
||||
└── 检查 motion.switchkins-type == 1
|
||||
▼
|
||||
xyzac-trt-kins inverse/forward
|
||||
▼
|
||||
XYZAC joint commands with TCP/RTCP compensation
|
||||
```
|
||||
|
||||
`M429` 对称地写入 `Q0`,切回 identity;`M430` 写入 `Q2`,进入 userk。`helix_ac.ngc` 演示了典型顺序:先用 M429 在 machine coordinates 安全定位,再用 M428 执行带 A/C 姿态变化的螺旋插补,结束后再切回 M429 做返回定位。
|
||||
|
||||
### 8.4 Vismach feedback
|
||||
|
||||
INI 中的 HAL nets 将以下反馈送到 `xyzac-trt-gui`:
|
||||
|
||||
```text
|
||||
joint.0.pos-fb → table-x
|
||||
joint.1.pos-fb → saddle-y
|
||||
joint.2.pos-fb → spindle-z
|
||||
joint.3.pos-fb → tilt-a
|
||||
joint.4.pos-fb → rotate-c
|
||||
motion.tooloffset.z → tool-offset
|
||||
xyzac-trt-kins.y-offset/z-offset → Vismach geometry
|
||||
```
|
||||
|
||||
因此 Vismach 不是运动 authority;它是 joint feedback、tool offset 和 geometry offset 的可视化消费者。
|
||||
|
||||
## 9. 状态真值与可观察结果
|
||||
|
||||
| 状态 | authority | 面板表现 |
|
||||
|---|---|---|
|
||||
| 急停 | TASK/motion state | E-stop active,模式按钮禁用 |
|
||||
| 上电 | TASK state + HAL | Power icon active,允许进一步操作 |
|
||||
| all homed | HAL status / joint state | MDI/AUTO 解除门控 |
|
||||
| 当前模式 | `stat.task_mode` | MANUAL/MDI/AUTO 页面切换 |
|
||||
| 当前文件 | `stat.file` / file-loaded status | 顶部文件名、G-code view、Run gate |
|
||||
| 解释器状态 | `stat.interp_state` | Run/Pause/Step/Stop 敏感度 |
|
||||
| 当前 G/M codes | `stat.gcodes` / `stat.mcodes` | G-Code 信息区域 |
|
||||
| 机床位置 | `stat.position` / joint feedback | DRO 和 Vismach |
|
||||
| kinematics mode | `motion.switchkins-type` | AXIS switchkins 面板/RTCP 模式 |
|
||||
|
||||
任何未来 Web/WASM 控制面板都应消费这些 authority 的 snapshot/event,而不是在 JS/GUI 层镜像一套可写的机床状态。
|
||||
|
||||
## 10. 现有证据与复现
|
||||
|
||||
```bash
|
||||
./run-gmoccapy.sh
|
||||
./run-5axis-rtcp.sh
|
||||
./run-5axis-rtcp.sh xyzbc-trt.ini
|
||||
```
|
||||
|
||||
已验证的五轴运行证据:
|
||||
|
||||
- [5axis-rtcp-running.png](5axis-rtcp-running.png)
|
||||
- [5axis-rtcp-shortcut.md](5axis-rtcp-shortcut.md)
|
||||
- `linuxcnc` 状态返回 `file=./demos/xyzac_switchkins.ngc`
|
||||
- AXIS 标题包含 `sim-xyzac-trt-kins (switchkins)`
|
||||
- `xyzac-trt-gui` 进程和五轴 Vismach 预览存在
|
||||
|
||||
## 11. 后续分析顺序
|
||||
|
||||
分析任何新的 GMOCCAPY/五轴控制问题时,按以下顺序取证:
|
||||
|
||||
1. 先记录实际运行时路径、LinuxCNC 版本、GMOCCAPY 版本;不要把系统安装脚本和仓库源码混为一份。
|
||||
2. 读取 INI 的 DISPLAY/TASK/HAL/RS274NGC/KINS/TRAJ/EMCIO 段,建立配置闭包。
|
||||
3. 在 Glade 中找到 widget ID,再在 `gmoccapy.py` 找 signal handler;不要只根据截图猜按钮语义。
|
||||
4. 追踪 handler 发出的 `linuxcnc.command()`,确认 command 进入 TASK/motion/HAL 的路径。
|
||||
5. 用 `linuxcnc.stat()`、HAL pins、日志和 Vismach feedback 交叉验证,不把 DRO 或 preview 当作 authority。
|
||||
6. 五轴问题先确认 kinematics type、M428/M429 状态、tool offset、rotation center/axis offset,再分析轨迹差异。
|
||||
7. 任何缺失的官方 oracle、实时硬件、浏览器或资源输入都保留为明确 blocker,不以 GUI 截图替代完整等价证明。
|
||||
7427
doc-g/gmoccapy-file-inventory.json
Normal file
18
doc-g/gmoccapy-file-inventory.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# GMOCCAPY 文件清单
|
||||
|
||||
清单范围内共 `1233` 个文件;每项包含相对路径、角色、字节数和 SHA-256。
|
||||
|
||||
| 角色 | 数量 |
|
||||
|---|---:|
|
||||
| `documentation` | 2 |
|
||||
| `documentation_asset` | 27 |
|
||||
| `icon_asset` | 973 |
|
||||
| `implementation` | 28 |
|
||||
| `installed_ui_asset` | 1 |
|
||||
| `simulation_asset` | 16 |
|
||||
| `simulation_ini` | 29 |
|
||||
| `simulation_resource` | 69 |
|
||||
| `simulation_runtime_data` | 64 |
|
||||
| `translation` | 24 |
|
||||
|
||||
完整逐文件数据见 [`gmoccapy-file-inventory.json`](gmoccapy-file-inventory.json)。
|
||||
BIN
doc-g/gmoccapy-homed.png
Normal file
|
After Width: | Height: | Size: 285 KiB |
BIN
doc-g/gmoccapy-homing.png
Normal file
|
After Width: | Height: | Size: 284 KiB |
BIN
doc-g/gmoccapy-hover-auto.png
Normal file
|
After Width: | Height: | Size: 296 KiB |
BIN
doc-g/gmoccapy-important-changes-v2.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
doc-g/gmoccapy-important-changes.png
Normal file
|
After Width: | Height: | Size: 197 KiB |
BIN
doc-g/gmoccapy-load-file-page.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
doc-g/gmoccapy-open-file-icon.png
Normal file
|
After Width: | Height: | Size: 486 B |
BIN
doc-g/gmoccapy-popup.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
doc-g/gmoccapy-program-loaded.png
Normal file
|
After Width: | Height: | Size: 295 KiB |
BIN
doc-g/gmoccapy-ui-load-confirmed.png
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
doc-g/root-current.png
Normal file
|
After Width: | Height: | Size: 5.7 MiB |