autocal layout first design
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
# Orisys AutoCal
|
||||
|
||||
Internal control application for the **5-axis calibration platform**, driving a
|
||||
ZMotion ZMC bus controller (EtherCAT or RTEX) from the PC.
|
||||
|
||||
Replaces the vendor example program it was ported from:
|
||||
|
||||
D:\orisys_ws\zmotion\Python64\例程9-总线控制运动
|
||||
|
||||
> **Internal use only.** AutoCal is not part of the customer installer. See
|
||||
> "Do not ship this app" below.
|
||||
|
||||
## What it does
|
||||
|
||||
- **Connect** over Ethernet (with an IP scan), serial, PCI, local/simulated, or
|
||||
a built-in **demo** transport that needs no hardware and no DLL.
|
||||
- **Initialise the bus** by downloading `ECAT.bas` or `RTEX.bas` to the
|
||||
controller, then follow the controller's own state machine to completion — or
|
||||
to a timeout, with the vendor's diagnostic hint.
|
||||
- **Read axis state** for all five drives at once: enable, axis type, pulse
|
||||
equivalent, speed, acceleration, commanded and measured position (in user
|
||||
units *and* millimetres), axis status word, motion and homing state.
|
||||
- **Move**: enable/disable, jog, absolute and relative moves, stop, stop-all,
|
||||
driver homing, clear a drive alarm. Every command is guarded — see below.
|
||||
- **Exchange raw bus data**: EtherCAT object-dictionary (SDO) reads and writes,
|
||||
and RTEX parameters. Each family is enabled only when the controller reports
|
||||
that bus type.
|
||||
|
||||
## Run it
|
||||
|
||||
PySide6 only — Qt's LGPLv3 binding, so an internal closed-source tool needs no
|
||||
licence purchase. (`PyQt5`, which the studio app uses, is GPLv3-or-commercial.)
|
||||
|
||||
```powershell
|
||||
# with the environment that already has PySide6 (Anaconda py310, for example)
|
||||
$env:PYTHONPATH = "D:\orisys_ws\orisys-app\shared\src;D:\orisys_ws\orisys-app\apps\autocal\src"
|
||||
python D:\orisys_ws\orisys-app\apps\autocal\run.py
|
||||
```
|
||||
|
||||
Or with no controller attached at all:
|
||||
|
||||
```powershell
|
||||
python .\apps\autocal\run.py --demo
|
||||
```
|
||||
|
||||
`run.py` sets `ORISYS_APP_ID=autocal` before importing anything from
|
||||
`orisys_app_shared`, so this app's `config/`, `preference.json` and `outputs/`
|
||||
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.
|
||||
|
||||
## Safety model
|
||||
|
||||
The guards live in `motion/service.py`, not in the UI, so they hold whatever
|
||||
calls them:
|
||||
|
||||
- **Motion is refused** while the bus is not initialised, while the axis is
|
||||
unknown, or on a non-finite target — with a reason, never a silent no-op.
|
||||
- **Homing needs a configured mode.** The vendor example pre-fills `17`, which
|
||||
is one specific drive's mode; AutoCal starts at `0` and refuses, so the
|
||||
operator chooses for the drive in front of them instead of inheriting a number
|
||||
from another machine.
|
||||
- **Homing is refused on RTEX**, as the vendor example itself does.
|
||||
- **Disconnect stops the axes first** (the vendor program closes the handle and
|
||||
leaves the platform coasting).
|
||||
- **SDO/RTEX are gated by the reported bus type**, so a write can never be aimed
|
||||
at the wrong family.
|
||||
- **A failed read is marked, not zeroed.** `ZmotionValue.value_or_none` returns
|
||||
`None` on failure; nothing silently treats a failed read's zero as a position.
|
||||
|
||||
The displayed millimetre value is *derived* from the pulse equivalent
|
||||
(bench-measured: `units=10` → 1000 user units = 2 mm). It is a display value,
|
||||
not a calibrated length, and the UI says so.
|
||||
|
||||
## Layout
|
||||
|
||||
```text
|
||||
apps/autocal/
|
||||
run.py # source-tree launcher
|
||||
config/
|
||||
preferences.default.json # shipped defaults
|
||||
locales/{zh_CN,en_US}.json
|
||||
bas/{ECAT,RTEX}.bas # copies: the vendor needs a non-Chinese path
|
||||
src/orisys_autocal/
|
||||
main.py # logging, locale, theme, window
|
||||
preferences.py # AutoCal's own preference schema
|
||||
motion/ # Qt-free except poller.py (the Qt worker bridge)
|
||||
models.py errors.py units.py service.py transport.py poller.py
|
||||
ui/ # window.py + one module per panel
|
||||
tools/ # panel_check.py, panel_snapshot.py
|
||||
tests/
|
||||
```
|
||||
|
||||
`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
|
||||
between threads. The UI talks to it through signals only.
|
||||
|
||||
## Verify
|
||||
|
||||
Both work with no display, no controller and no DLL:
|
||||
|
||||
```powershell
|
||||
python .\apps\autocal\tools\panel_check.py # 120 layout/theme/locale/gating checks
|
||||
python .\apps\autocal\tools\panel_snapshot.py --demo --theme dark --out shot.png
|
||||
python .\apps\autocal\tools\panel_snapshot.py --compare --out tabs.png # one image per tab
|
||||
python .\apps\autocal\tools\layout_mockup.py --out layout.png # proposed layout, drawn
|
||||
python -m pytest apps\autocal\tests # offscreen suite
|
||||
```
|
||||
|
||||
`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.
|
||||
|
||||
`panel_check.py` builds the real window in both themes at 1280×720, 1600×900 and
|
||||
1920×1080, asserts that no widget escapes its parent, that motion is
|
||||
unreachable before the bus is initialised, that the SDO/RTEX panels follow the
|
||||
reported bus family, and then runs a full simulated session from `ECAT.bas`.
|
||||
|
||||
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 no binding at
|
||||
all, and the controller interface must stay importable headless.
|
||||
|
||||
## Do not ship this app
|
||||
|
||||
AutoCal is an internal tool. It is deliberately **not** packaged by
|
||||
`build/studio`, whose PyInstaller spec must not learn about the ZMotion runtime
|
||||
DLLs, and it is not in the customer installer. The ZMotion SDK is vendor-supplied
|
||||
hardware support software with its own terms; do not redistribute those DLLs
|
||||
with a customer product.
|
||||
Reference in New Issue
Block a user