重构 Autocal 采集页:单一队列与控制器驱动的主按钮
选点、对准、加载与录像分散在两个面板,状态靠隐藏相位判断。现改由不依赖 Qt 的采集控制器驱动,并加入录像选项、汇总平铺副本、保持到时自动结束、会话结束回到空闲,以及无设备时的模拟采集。已运行 Autocal 非 Qt 测试并通过范围内门禁;界面测试待 Windows 复核,未做实机验收。
This commit is contained in:
+31
-32
@@ -26,11 +26,20 @@ Replaces the vendor example program it was ported from:
|
||||
and RTEX parameters. Each family is enabled only when the controller reports
|
||||
that bus type.
|
||||
|
||||
## Capture workflow
|
||||
|
||||
The 标定采集 page is one queue, not two panels — a single point is a queue with one
|
||||
row. `Ready` lists the six facts a run needs and the **primary button follows the
|
||||
step**, always stating why it is disabled. Alignment is only a request: the move
|
||||
goes through the guarded pose-move path, and only a completed move unlocks capture.
|
||||
Recording options (video, codec, lossless frames) are chosen before the session and
|
||||
written once into `session.json`; an unavailable codec is disabled with its reason,
|
||||
never silently substituted. [docs/workflow.md](docs/workflow.md) has the detail.
|
||||
|
||||
## Run it
|
||||
|
||||
PySide6 only — Qt's LGPLv3 binding, so an internal closed-source tool needs no
|
||||
licence purchase. (PyQt5 is GPLv3-or-commercial; the studio app moved to PySide6
|
||||
for the same reason and no longer uses it.)
|
||||
licence purchase (PyQt5 is GPLv3-or-commercial).
|
||||
|
||||
```powershell
|
||||
# with the environment that already has PySide6 (Anaconda py310, for example)
|
||||
@@ -50,12 +59,9 @@ resolve to `apps/autocal/` rather than the studio's.
|
||||
|
||||
### Controller library
|
||||
|
||||
The ZMotion runtime is vendored at `shared/resources/zmotion/x64/` (see the
|
||||
README beside it for provenance). Point `ORISYS_ZMOTION_DLL_DIR` at another
|
||||
directory to use a different build — on a motion controller, silently loading
|
||||
the wrong runtime version is the kind of fault that looks like a working machine
|
||||
until it moves, so the override is authoritative: if it does not contain
|
||||
`zauxdll.dll`, the app refuses to connect instead of quietly falling back.
|
||||
The ZMotion runtime is vendored at `shared/resources/zmotion/x64/`. `ORISYS_ZMOTION_DLL_DIR` overrides it, and the
|
||||
override is authoritative: a directory without `zauxdll.dll` makes the app refuse
|
||||
to connect rather than quietly load another build.
|
||||
|
||||
One contract in `shared/src/orisys_app_shared/zmotion/client.py` is worth
|
||||
knowing before touching it: `ZAux_Execute` **must** be given a reply buffer.
|
||||
@@ -71,8 +77,7 @@ The vendor sequence, with the checks AutoCal's own state machine adds. P1 of
|
||||
|
||||
1. **Connect** (`eth` + the controller's IP). On success the app adopts any BAS
|
||||
program the controller is already running from a previous session instead of
|
||||
claiming the bus is uninitialised; `BUS_TYPE` is read immediately, and
|
||||
`Bus_InitStatus` on the first poll.
|
||||
claiming the bus is uninitialised; `BUS_TYPE` is read immediately.
|
||||
2. **Download only if the bus type is unknown.** `ECAT.bas` / `RTEX.bas` are
|
||||
chosen from `config/bas/` (the vendor requires a non-Chinese path) and
|
||||
written to ROM. A controller that still has the program needs no download.
|
||||
@@ -97,7 +102,7 @@ The vendor sequence, with the checks AutoCal's own state machine adds. P1 of
|
||||
| Range | Family | What the operator should do |
|
||||
|---|---|---|
|
||||
| `0` | success | — |
|
||||
| `20000–29999` | PC-library fault (handshake, socket, address) | check the cable/segment and the IP, make sure nothing else holds the connection, then retry. Frequently transient: a first open on a cold route can fail and succeed on the next attempt, which is why an Ethernet connect makes up to 3 attempts before reporting. |
|
||||
| `20000–29999` | PC-library fault (handshake, socket, address) | check the cable/segment and the IP, then retry; a first open on a cold route is often transient, which is why an Ethernet connect makes up to 3 attempts. |
|
||||
| `30000+` | the auxiliary block `zauxdll2.h` documents | the message names it (`无应答`, `参数错误`, …) |
|
||||
| anything else | the controller's own code | look it up in ZDevelop's table |
|
||||
|
||||
@@ -158,6 +163,10 @@ apps/autocal/
|
||||
tests/
|
||||
```
|
||||
|
||||
`capture_controller.py` is the capture page's Qt-free step machine (queue, `Ready`,
|
||||
one primary action, remeasure/skip/stop, poll); `recording_options.py` turns the
|
||||
video choice into a `RecordConfig` and probes which codecs this machine can write.
|
||||
|
||||
`motion/poller.py` is the only place a controller call can originate. It owns
|
||||
the DLL handle on its own thread, so the vendor library's blocking calls (a ROM
|
||||
write, a bus scan) never freeze the window and the ctypes handle is never shared
|
||||
@@ -177,26 +186,18 @@ python -m pytest apps\autocal\tests # offscreen suite
|
||||
```
|
||||
|
||||
Connection and bus initialisation are used once per machine start-up, so they live
|
||||
in a **modeless device-setup dialog** (`ui/device_setup.py`) rather than the rail:
|
||||
the rail carries what is read while working — the axis readout and the collapsed
|
||||
SDO/RTEX monitor. The dialog opens from the status strip's 设备设置 button, from the
|
||||
connection/bus/init chips (each opens the tab that explains it) or from
|
||||
文件 → 设备设置, and the strip keeps reporting the live state whether it is open or
|
||||
shut.
|
||||
in a **modeless device-setup dialog** (`ui/device_setup.py`), opened from the status
|
||||
strip, its chips, or 文件 → 设备设置. The rail carries what is read while working:
|
||||
the axis readout and the collapsed SDO/RTEX monitor.
|
||||
|
||||
`preview_snapshot.py` is the one tool that can photograph the **3D pane**, which
|
||||
`panel_snapshot.py` cannot: the offscreen platform has no OpenGL context at all, so
|
||||
the pane is empty in those shots. It needs the real platform plugin and a visible
|
||||
window, and it writes either the pane alone or the whole page with the pane
|
||||
composited in. What the view draws, and what each colour means, is in
|
||||
`preview_snapshot.py` is the only tool that can photograph the **3D pane** (the
|
||||
offscreen platform has no OpenGL context, so the pane is empty there). It needs
|
||||
the real platform plugin and a visible window. See
|
||||
[docs/finger-preview.md](docs/finger-preview.md).
|
||||
|
||||
`layout_mockup.py` is a **wireframe, not the application**: it paints the
|
||||
proposed single-page layout with the shared theme tokens and imports nothing
|
||||
from the app, so the layout can be reviewed and marked up without building the
|
||||
panels. It draws the full page plus previews of the other workspace tabs in one
|
||||
canvas, and `--page setup` draws the device-setup dialog over the page it opens
|
||||
from.
|
||||
`layout_mockup.py` is a **wireframe, not the application**: it paints the proposed
|
||||
single-page layout with the shared theme tokens and imports nothing from the app,
|
||||
so the layout can be reviewed without building the panels.
|
||||
|
||||
`panel_check.py` builds the real window in both themes at 1440×900 and
|
||||
1920×1080, asserts that no widget escapes its parent, that motion is
|
||||
@@ -207,10 +208,8 @@ The suite is written for **PySide6**. Where PySide6 is missing, the test modules
|
||||
are skipped rather than failed: a broken environment should not look like a
|
||||
broken application. `tests/test_binding_purity.py` enforces the binding policy
|
||||
statically — no module may import PyQt5/PySide2/PyQt6, no module may use an
|
||||
unscoped Qt enumerator (removed in PySide6), `shared/` may import PySide6 and no
|
||||
other binding, and the controller interface must stay importable headless. The
|
||||
studio app enforces the same rules for itself in
|
||||
`apps/studio/tests/test_binding_purity.py`.
|
||||
unscoped Qt enumerator, `shared/` may import PySide6 and no other binding, and
|
||||
the controller interface must stay importable headless.
|
||||
|
||||
## Do not ship this app
|
||||
|
||||
|
||||
Reference in New Issue
Block a user