migrate pyqt5 to pyside6

This commit is contained in:
ykcheungab
2026-09-21 09:34:56 +08:00
parent e276b5de0f
commit ccedb9ddd9
66 changed files with 2184 additions and 395 deletions
+167
View File
@@ -0,0 +1,167 @@
# Calibration sequence format (`.oryseq`)
A calibration sequence is a small JSON document: the **profile** that generated
the points, plus the **points** themselves. It is written by
`导出序列…` in the Sequence tab and read by `导入序列…`.
Why JSON and not a binary container: a sequence is at most a few hundred points
of text. It is read by a human, diffed in review, and emailed to whoever runs the
bench. Binary would buy nothing and cost all three. (The *acquired data* of a run
is a different problem — per-point time series are large and will need a sidecar;
that decision belongs to workflow P8, not here.)
## Conventions
The format follows this repository's existing documents rather than inventing a
convention. Where it matches `session.json` and `force_calibration.json`, that is
deliberate:
| Convention | Value |
|---|---|
| Version field | `"format_version": "1.0"` — a dotted **string**, compared as a tuple |
| Identity | `"kind": "autocal.sequence"` — checked on import |
| Extension | `.oryseq` by convention; import accepts any `.json` |
| Encoding | UTF-8, `indent=2`, `ensure_ascii=False`, trailing newline |
| Write | temp file then `Path.replace` — a half-written sequence that still parses looks like a design |
## The document
```json
{
"format_version": "1.0",
"kind": "autocal.sequence",
"created_at": "2026-09-20T10:31:00+00:00",
"name": "5 点灵敏度扫描",
"note": "",
"versions": { "app": "0.1.0", "shared": "0.1.0", "shared_api": "0.1" },
"device": {
"sensor_type": "box",
"config_ref": "…\\config\\sensors\\box\\sensor.json",
"geometry_id": "finger-1.0.0",
"probe_tip_set": true
},
"frame": { "targets": "platform", "length_unit": "mm", "units_per_mm": 10.0 },
"profile": {
"mode": "flat", "pitch_mm": 2.0, "margin_mm": 1.0, "radius_mm": 0.0,
"clear_height_mm": 5.0, "approach_speed_mm_s": 2.0, "press_speed_mm_s": 0.5,
"press_depth_mm": 0.2, "dwell_s": 0.5, "settle_window_s": 0.5,
"repeats": 3, "order": "serpentine"
},
"points": [
{
"id": 1,
"target_mm": { "x": 0.0, "y": 0.0, "z": 12.4 },
"sensor_mm": { "x": 0.0, "y": 0.0, "z": 0.0 },
"clearance_mm": 5.0,
"enabled": true,
"note": ""
}
],
"summary": {
"count": 12, "enabled": 12,
"min_mm": { "x": 0.0, "y": 0.0, "z": 12.4 },
"max_mm": { "x": 10.0, "y": 4.0, "z": 12.4 },
"span_mm": { "x": 10.0, "y": 4.0, "z": 0.0 },
"pitch_mm": 2.0,
"estimated_duration_s": 380.0
}
}
```
## Fields
### `frame` — required
| Field | Meaning |
|---|---|
| `targets` | the frame `target_mm` is expressed in; today always `"platform"` |
| `length_unit` | `"mm"`; anything else is refused |
| `units_per_mm` | the pulse equivalent the points were built with |
A sequence whose numbers do not say whether they are millimetres or user units is
a sequence that will eventually be read wrong. `units_per_mm` is recorded so a
file from a differently-configured axis is detectable rather than silently
believed.
### `profile` — required
The twelve fields the Sequence tab edits, and nothing else. A field absent from
the file is **kept at its current value and reported**, never silently defaulted
to another bench's number.
### `points` — required, may be empty
| Field | Meaning |
|---|---|
| `id` | 1-based, stable within the file; used for run progress and resume |
| `target_mm` | where the axes go — required, with all three of `x`, `y`, `z` |
| `sensor_mm` | where that point lands on the sensor; optional but written |
| `clearance_mm` | the standoff for this point |
| `enabled` | false = designed but deliberately skipped |
| `note` | free text |
Both positions are stored on purpose. The platform target is what the run uses;
the sensor position is what makes the point checkable against the surface.
Deriving one from the other on every load would silently change what the points
mean the moment a sensor is re-fixtured — and a re-fixture is exactly when a
stored transform should be *noticed*, not recomputed from.
### `summary` — derived, not authoritative
Present so a human reading the file sees the point count and travel span without
counting rows, and so a diff is legible. It is **recomputed on load**; a mismatch
is reported as a warning and the recomputed values win. That is how a hand-edited
document announces itself instead of quietly disagreeing with its own contents.
`estimated_duration_s` assumes a constant traverse rate
(`sequence_io.ASSUMED_TRAVERSE_MM_S`) and ignores acceleration. It is a planning
figure and is labelled an estimate wherever it is shown.
### `device` — a reference, not a requirement
Records which sensor the sequence was designed for. Importing a sequence for
another sensor is **allowed and warned about**, because re-fixturing
deliberately is a normal thing to do.
## Import rules
Every rejection names the specific problem — `invalid file` tells an operator
nothing.
| Condition | Behaviour |
|---|---|
| not JSON | refused, with line and column |
| top level not an object | refused |
| `kind` missing or different | refused, naming what was found |
| `format_version` missing or not a string | refused |
| `format_version` newer than this build | refused, naming both versions |
| `frame` missing, or `length_unit` not `mm` | refused |
| `profile` not an object | refused |
| a profile key absent | imported; the current value is kept and the key is reported |
| `points` absent | imported as profile-only, and reported |
| a point without `target_mm.x/y/z` | refused, naming the point and the axis |
| `summary` disagreeing with `points` | imported; recomputed, and reported |
| current design is unsaved | the operator is asked before it is replaced |
Version strings are compared as tuples, never as strings: `"1.10"` is newer than
`"1.9"`, and a string comparison would make a newer file look older and load it
as if it were supported.
## Compatibility rules
- **Adding an optional key** does not change `format_version`.
- **Adding a required key, or changing a field's meaning or unit**, does.
- A reader must refuse a document whose version it does not support rather than
interpreting the parts it recognises — a sequence half-applied is worse than
one rejected.
- `1.x` readers are expected to accept `1.y` for `y <= x` on the same major
version and report anything they did not use.
## Where the pieces live
| Concern | Module |
|---|---|
| The document, validation, the derived summary | `apps/autocal/src/orisys_autocal/sequence_io.py` |
| The design screen and its controls | `apps/autocal/src/orisys_autocal/ui/sequence_panel.py` |
| The file dialogs and the unsaved-work guard | `apps/autocal/src/orisys_autocal/ui/window.py` |
| Why a sequence is an artifact (P8) | `apps/autocal/docs/workflow.md` |