migrate pyqt5 to pyside6
This commit is contained in:
@@ -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` |
|
||||
Reference in New Issue
Block a user