3e45ecdc3c
菜单栏为文件 | 采集 | 分析 | 视图 | 帮助。提示里指向这两项的路径已改成「分析」。
521 lines
43 KiB
JSON
521 lines
43 KiB
JSON
{
|
|
"version": 1,
|
|
"scope": "apps/studio",
|
|
"note": "Authored, not generated: docs/ui-elements.json is the machine-readable half of the layout contract in docs/ui-layout.md, and apps/studio/tests/test_ui_elements.py fails when the window and this file disagree. tools/ui_elements_dump.py drafts it from a live window; its output must be curated, never copied. An element is found by 'path' (dot path from the window, with '()' for a Qt getter, '[i]' for an index and '.' for the window itself) or by a unique 'objectName'. 'objectName' is recorded only when the name is a stable identity; 'style' records the stylesheet token a widget carries instead (the rail's transport is 'primaryButton' while idle and 'ghostButton' while acquiring, so it has no name of its own). 'condition' names a predicate from ui/elements.py CONDITIONS: the element is expected only when it holds, and the suite has a case per condition so no element is permanently skipped. 'attrs' records a structural property of the widget itself (a table's column count, a splitter's child count). The drift report compares the visible widgets' objectNames against the element identities plus the style_tokens below (Qt's own 'qt_' names excluded), and views.<view>.unlisted_named_widgets records what is deliberately left over; it is empty because every name on screen is accounted for. 'rows' expands a per-channel template into one element per force axis.",
|
|
"window": {
|
|
"id": "main_window",
|
|
"path": ".",
|
|
"objectName": null,
|
|
"class": "StudioMainWindow",
|
|
"purpose": "the shell: brand bar, rail, splitter, menus and status bar on one window",
|
|
"views": "all"
|
|
},
|
|
"views": {
|
|
"live": {
|
|
"purpose": "live measurement: the measurement panes lead, the charts and the rail's settings accompany them",
|
|
"primary": ["measurement_2d", "measurement_3d"],
|
|
"companions": ["charts"],
|
|
"unlisted_named_widgets": []
|
|
},
|
|
"replay": {
|
|
"purpose": "history browsing: the charts lead, the measurement panes stay beside them, and the rail shows a recorded video instead of a camera",
|
|
"primary": ["charts"],
|
|
"companions": ["measurement_2d", "measurement_3d"],
|
|
"unlisted_named_widgets": []
|
|
},
|
|
"calibration": {
|
|
"purpose": "force calibration: cal_panel's six-column table and action row lead, and the charts remain on screen beside it",
|
|
"primary": ["cal_panel"],
|
|
"companions": ["charts"],
|
|
"unlisted_named_widgets": []
|
|
}
|
|
},
|
|
"style_tokens": [
|
|
{ "name": "navButton", "purpose": "brand-bar view tab" },
|
|
{ "name": "sectionSeparator", "purpose": "1 px rule between groups" },
|
|
{ "name": "panelTitle", "purpose": "panel and section heading" },
|
|
{ "name": "panelMeta", "purpose": "secondary readout beside a heading" },
|
|
{ "name": "hintText", "purpose": "explanation or state line under a control" },
|
|
{ "name": "fieldLabel", "purpose": "label of a form field" },
|
|
{ "name": "unitBadge", "purpose": "small filled badge: unit, channel or product" },
|
|
{ "name": "valueCard", "purpose": "large monospace numeric reading" },
|
|
{ "name": "valueGroup", "purpose": "container of one reading's card" },
|
|
{ "name": "cardPanel", "purpose": "raised container for a picture or a plot" },
|
|
{ "name": "contentPanel", "purpose": "the panel frame every splitter slot uses" },
|
|
{ "name": "primaryButton", "purpose": "the filled action of the moment" },
|
|
{ "name": "ghostButton", "purpose": "outline action" },
|
|
{ "name": "dangerOutline", "purpose": "outline stop/cancel action" },
|
|
{ "name": "dangerButton", "purpose": "filled stop/record-stop action" }
|
|
],
|
|
"regions": [
|
|
{
|
|
"id": "brand_bar",
|
|
"path": "brand_bar",
|
|
"objectName": "brandBar",
|
|
"class": "BrandBar",
|
|
"purpose": "the three top-level view tabs, the lockup and the product name",
|
|
"elements": [
|
|
{ "id": "brand_logo", "path": "brand_bar.logo", "objectName": "brandLogo", "class": "QLabel",
|
|
"purpose": "corporate mark, re-tinted from the theme; the text lockup stays if the asset is missing" },
|
|
{ "id": "brand_wordmark", "path": "brand_bar.name", "objectName": "brandName", "class": "QLabel",
|
|
"purpose": "product wordmark beside the mark" },
|
|
{ "id": "brand_tagline", "path": "brand_bar.tagline", "objectName": "brandTagline", "class": "QLabel",
|
|
"purpose": "tagline: kept in the layout but hidden in the one-line rail, where it is the wordmark's tooltip" },
|
|
{ "id": "brand_product", "path": "brand_bar.product", "objectName": "brandProduct", "class": "QLabel",
|
|
"purpose": "product title, right-aligned at the end of the bar" },
|
|
{ "id": "brand_divider", "path": "brand_bar.divider", "objectName": null, "style": "sectionSeparator", "class": "QFrame",
|
|
"purpose": "1 px rule between the tabs and the product title" },
|
|
{ "id": "tab_live", "path": "brand_bar.btn_live", "objectName": null, "style": "navButton", "class": "QPushButton",
|
|
"purpose": "the live (measurement) view tab; its checked state marks the active view" },
|
|
{ "id": "tab_replay", "path": "brand_bar.btn_playback", "objectName": null, "style": "navButton", "class": "QPushButton",
|
|
"purpose": "the replay (history) view tab" },
|
|
{ "id": "tab_calibration", "path": "brand_bar.btn_calibration", "objectName": null, "style": "navButton", "class": "QPushButton",
|
|
"purpose": "the calibration view tab" }
|
|
]
|
|
},
|
|
{
|
|
"id": "rail",
|
|
"path": "toolbar",
|
|
"objectName": null,
|
|
"class": "ToolbarWidget",
|
|
"purpose": "the status rail: sensor summary, session phase, capture source, record state, transport and the settings toggle",
|
|
"elements": [
|
|
{ "id": "sensor_button", "path": "toolbar.btn_sensor", "objectName": null, "style": "ghostButton", "class": "QPushButton",
|
|
"purpose": "sensor and config summary ('BOX . box/sensor.json'); opens the sensor dialog" },
|
|
{ "id": "phase_chip", "path": "toolbar.status_chip", "objectName": "statusChip", "class": "QLabel",
|
|
"purpose": "session phase (idle / connecting / running / error)" },
|
|
{ "id": "source_summary", "path": "toolbar.source_summary", "objectName": null, "style": "panelMeta", "class": "ShrinkableLabel",
|
|
"purpose": "capture-source detail (rate, video or recording name); hidden while empty, with the whole text in its tooltip" },
|
|
{ "id": "calibration_summary", "path": "toolbar.calibration_label", "objectName": null, "style": "panelMeta", "class": "ShrinkableLabel",
|
|
"purpose": "which calibration the device confirmed; hidden while empty" },
|
|
{ "id": "data_state", "path": "toolbar.data_state", "objectName": null, "style": "panelMeta", "class": "ShrinkableLabel",
|
|
"purpose": "data-validity readout; hidden until a sample has been judged" },
|
|
{ "id": "record_chip", "path": "toolbar.record_chip", "objectName": "recordChip", "class": "RecordStatusChip",
|
|
"purpose": "record state, elapsed time and what the writer dropped" },
|
|
{ "id": "source_slot", "path": "toolbar.source_slot", "objectName": null, "class": "QWidget",
|
|
"purpose": "the one capture-source slot; the view decides which of the three groups inside it is shown" },
|
|
{ "id": "camera_group", "path": "toolbar.camera_group", "objectName": null, "class": "QWidget", "views": ["live"],
|
|
"purpose": "live camera picker group (camera index and search)" },
|
|
{ "id": "camera_id_label", "path": "toolbar.cam_id_label", "objectName": null, "style": "fieldLabel", "class": "QLabel", "views": ["live"],
|
|
"purpose": "label of the camera index field" },
|
|
{ "id": "camera_id_spin", "path": "toolbar.cam_id_spin", "objectName": null, "class": "QSpinBox", "views": ["live"],
|
|
"purpose": "camera index the next run opens" },
|
|
{ "id": "camera_search", "path": "toolbar.btn_search", "objectName": null, "class": "QPushButton", "views": ["live"],
|
|
"purpose": "probe the cameras and offer the ones found" },
|
|
{ "id": "video_group", "path": "toolbar.video_group", "objectName": null, "class": "QWidget", "views": ["live"], "condition": "video_source",
|
|
"purpose": "shown instead of the camera picker while the run source is a file" },
|
|
{ "id": "use_camera", "path": "toolbar.btn_use_camera", "objectName": null, "class": "QPushButton", "views": ["live"], "condition": "video_source",
|
|
"purpose": "one-click way back to the camera source" },
|
|
{ "id": "replay_group", "path": "toolbar.replay_group", "objectName": null, "class": "QWidget", "views": ["replay"],
|
|
"purpose": "the replay view's source group" },
|
|
{ "id": "replay_video", "path": "toolbar.btn_replay_video", "objectName": null, "class": "QPushButton", "views": ["replay"],
|
|
"purpose": "choose the recorded video to play" },
|
|
{ "id": "recompute", "path": "toolbar.btn_recompute", "objectName": null, "class": "QPushButton", "views": ["replay"],
|
|
"purpose": "re-run the vision pipeline over that video: the only processed picture a recording can produce" },
|
|
{ "id": "record", "path": "toolbar.btn_record", "objectName": null, "style": "primaryButton / dangerButton", "class": "QPushButton",
|
|
"purpose": "start or stop the recording; enabled with a live session and always enabled while recording" },
|
|
{ "id": "transport", "path": "toolbar.btn_run", "objectName": null, "style": "primaryButton / ghostButton", "class": "QPushButton",
|
|
"purpose": "the transport of both views: play / pause / resume; unchecked means paused while a session exists and idle when none does" },
|
|
{ "id": "stop", "path": "toolbar.btn_stop", "objectName": null, "style": "dangerOutline / dangerButton", "class": "QPushButton",
|
|
"purpose": "end the live session, leave the replay, or cancel a connect that has not returned" },
|
|
{ "id": "reset", "path": "toolbar.btn_reset", "objectName": null, "class": "QPushButton",
|
|
"purpose": "one control, one meaning per view: in the live view it zeroes the sensor tracker (enabled by the session state, never by which panel is on screen, and the display baseline restarts with it); in the replay view, which owns no device, it resets the display" },
|
|
{ "id": "settings_toggle", "path": "toolbar.btn_settings", "objectName": null, "style": "ghostButton", "class": "QPushButton",
|
|
"purpose": "expand or collapse the settings area" }
|
|
]
|
|
},
|
|
{
|
|
"id": "settings_area",
|
|
"path": "toolbar.settings_page",
|
|
"objectName": null,
|
|
"class": "QWidget",
|
|
"purpose": "the collapsible settings area: what is configuration rather than state",
|
|
"elements": [
|
|
{ "id": "settings_scroll", "path": "toolbar._settings_scroll", "objectName": null, "class": "QScrollArea",
|
|
"purpose": "the settings area scrolls sideways so a narrow window squeezes nothing into an unreadable row" },
|
|
{ "id": "preview", "path": "toolbar.btn_preview", "objectName": null, "class": "QPushButton",
|
|
"purpose": "live camera preview on/off; offered only for a camera source in the live view" },
|
|
{ "id": "units_label", "path": "toolbar.units_label", "objectName": null, "style": "fieldLabel", "class": "QLabel",
|
|
"purpose": "label of the display-unit field" },
|
|
{ "id": "units_combo", "path": "toolbar.units_combo", "objectName": null, "class": "QComboBox", "attrs": { "count": 2 },
|
|
"purpose": "display unit for the charts and the readings: newtons or raw counts" },
|
|
{ "id": "record_mode_label", "path": "toolbar.record_label", "objectName": null, "style": "fieldLabel", "class": "QLabel",
|
|
"purpose": "label of the record-mode field" },
|
|
{ "id": "record_mode_combo", "path": "toolbar.record_mode_combo", "objectName": null, "class": "QComboBox", "attrs": { "count": 2 },
|
|
"purpose": "what a recording writes: video + data, or data only" }
|
|
]
|
|
},
|
|
{
|
|
"id": "menus",
|
|
"path": "menuBar()",
|
|
"objectName": null,
|
|
"class": "QMenuBar",
|
|
"purpose": "the menu bar, its menus, and the actions they hold (plus the Space shortcut, which has no menu entry)",
|
|
"elements": [
|
|
{ "id": "file_menu", "path": "file_menu", "objectName": null, "class": "QMenu",
|
|
"purpose": "open a video, reveal the sensor config, open a recording for playback" },
|
|
{ "id": "session_menu", "path": "session_menu", "objectName": null, "class": "QMenu",
|
|
"purpose": "acquisition session controls: run, cancel, preview, tracker reset, pause" },
|
|
{ "id": "analysis_menu", "path": "analysis_menu", "objectName": null, "class": "QMenu",
|
|
"purpose": "offline analysis of a recording: serve or run a pass, and reopen analysis setup" },
|
|
{ "id": "view_menu", "path": "view_menu", "objectName": null, "class": "QMenu",
|
|
"purpose": "what is on screen: panels, 3D, display aids, reset views, theme and language" },
|
|
{ "id": "theme_menu", "path": "theme_menu", "objectName": null, "class": "QMenu",
|
|
"purpose": "the dark / light submenu of the view menu" },
|
|
{ "id": "lang_menu", "path": "lang_menu", "objectName": null, "class": "QMenu",
|
|
"purpose": "the Chinese / English submenu of the view menu" },
|
|
{ "id": "help_menu", "path": "help_menu", "objectName": null, "class": "QMenu",
|
|
"purpose": "the about entry" },
|
|
{ "id": "act_open_video", "path": "act_open_video", "objectName": null, "class": "QAction",
|
|
"purpose": "file menu: open a recorded video in the replay view (enabled in the replay view only)" },
|
|
{ "id": "act_reveal_config", "path": "act_reveal_config", "objectName": null, "class": "QAction",
|
|
"purpose": "file menu: show the active sensor config in the file manager" },
|
|
{ "id": "act_playback", "path": "act_playback", "objectName": null, "class": "QAction",
|
|
"purpose": "file menu: open a recorded session for playback" },
|
|
{ "id": "act_playback_latest", "path": "act_playback_latest", "objectName": null, "class": "QAction",
|
|
"purpose": "file menu: open the most recent recording" },
|
|
{ "id": "act_play", "path": "act_play", "objectName": null, "class": "QAction",
|
|
"purpose": "Space: the transport shortcut, a window action with no menu entry" },
|
|
{ "id": "act_run", "path": "act_run", "objectName": null, "class": "QAction",
|
|
"purpose": "session menu / F5: start a run (the rail's transport is the primary control)" },
|
|
{ "id": "act_stop", "path": "act_stop", "objectName": null, "class": "QAction",
|
|
"purpose": "session menu / Esc: stop the session or the replay" },
|
|
{ "id": "act_preview", "path": "act_preview", "objectName": null, "class": "QAction",
|
|
"purpose": "session menu / F6: camera preview on/off" },
|
|
{ "id": "act_reset", "path": "act_reset", "objectName": null, "class": "QAction",
|
|
"purpose": "session menu / Ctrl+R: the rail's reset on a second surface, named from the view it is in (reset sensor tracker while measuring, reset display while replaying)" },
|
|
{ "id": "act_pause", "path": "act_pause", "objectName": null, "class": "QAction",
|
|
"purpose": "session menu / F7: pause/resume the live session, the same state as the transport" },
|
|
{ "id": "act_cal_panel", "path": "act_cal_panel", "objectName": null, "class": "QAction",
|
|
"purpose": "view menu: show the calibration panel (checking it enters the calibration view, unchecking returns to live)" },
|
|
{ "id": "act_finger_3d", "path": "act_finger_3d", "objectName": null, "class": "QAction",
|
|
"purpose": "view menu: the 3D pane on/off, the same state as the pane's own switch; enabled for a finger sensor" },
|
|
{ "id": "act_scene3d_settings", "path": "act_scene3d_settings", "objectName": null, "class": "QAction",
|
|
"purpose": "view menu: open the 3D display settings dialog (the pane's gear is the same call)" },
|
|
{ "id": "act_show_fps", "path": "act_show_fps", "objectName": null, "class": "QAction",
|
|
"purpose": "view menu: show the frame-rate readout" },
|
|
{ "id": "act_show_diagnostics", "path": "act_show_diagnostics", "objectName": null, "class": "QAction",
|
|
"purpose": "view menu: how much the panels explain themselves around their pictures" },
|
|
{ "id": "act_settings", "path": "act_settings", "objectName": null, "class": "QAction",
|
|
"purpose": "view menu: the rail's settings area, the same state as its toggle button" },
|
|
{ "id": "act_plot_settings", "path": "act_plot_settings", "objectName": null, "class": "QAction",
|
|
"purpose": "view menu: chart window, ranges and acquisition filters (the chart gear is the same call)" },
|
|
{ "id": "act_rotate_arrows", "path": "act_rotate_arrows", "objectName": null, "class": "QAction",
|
|
"purpose": "view menu: rotate the 2D arrow field by 90 degrees for the mounted sensors" },
|
|
{ "id": "act_recompute", "path": "act_recompute", "objectName": null, "class": "QAction",
|
|
"purpose": "analysis menu: analyze video — serve a stored result or run the pass; same state as the rail's button" },
|
|
{ "id": "act_analysis_setup", "path": "act_analysis_setup", "objectName": null, "class": "QAction",
|
|
"purpose": "analysis menu: reopen the analysis setup dialog (profile, calibration, reference frame)" },
|
|
{ "id": "act_reset_view", "path": "act_reset_view", "objectName": null, "class": "QAction",
|
|
"purpose": "view menu / F8: reset the views (curves, window, cursor, 3D camera) without touching the device" },
|
|
{ "id": "act_theme_dark", "path": "act_theme_dark", "objectName": null, "class": "QAction",
|
|
"purpose": "theme submenu: dark theme" },
|
|
{ "id": "act_theme_light", "path": "act_theme_light", "objectName": null, "class": "QAction",
|
|
"purpose": "theme submenu: light theme" },
|
|
{ "id": "act_lang_zh", "path": "act_lang_zh", "objectName": null, "class": "QAction",
|
|
"purpose": "language submenu: Chinese" },
|
|
{ "id": "act_lang_en", "path": "act_lang_en", "objectName": null, "class": "QAction",
|
|
"purpose": "language submenu: English" },
|
|
{ "id": "act_about", "path": "act_about", "objectName": null, "class": "QAction",
|
|
"purpose": "help menu: the about dialog" }
|
|
]
|
|
},
|
|
{
|
|
"id": "status_bar",
|
|
"path": "statusBar()",
|
|
"objectName": null,
|
|
"class": "_NoticeBar",
|
|
"purpose": "the one-off notice line: the expiring-message QStatusBar subclass; session states live on the rail, not here",
|
|
"elements": []
|
|
},
|
|
{
|
|
"id": "content",
|
|
"path": "content_splitter",
|
|
"objectName": null,
|
|
"class": "QSplitter",
|
|
"attrs": { "count": 4 },
|
|
"purpose": "the content splitter: the measurement region and its companions, in panel order (3D, 2D, charts, calibration); its children are the panes below",
|
|
"elements": []
|
|
},
|
|
{
|
|
"id": "sensor_picker",
|
|
"path": "sensor_picker",
|
|
"objectName": null,
|
|
"class": "SensorPickerWidget",
|
|
"purpose": "the sensor type and config pickers the rail summarises in one button; the dialog is where they live",
|
|
"elements": [
|
|
{ "id": "picker_badge", "path": "sensor_picker.thumb_label", "objectName": null, "style": "unitBadge", "class": "QLabel",
|
|
"purpose": "the selected product's name as a badge" },
|
|
{ "id": "picker_type_label", "path": "sensor_picker.type_label", "objectName": null, "style": "fieldLabel", "class": "QLabel",
|
|
"purpose": "label of the sensor-type field" },
|
|
{ "id": "picker_type_combo", "path": "sensor_picker.type_combo", "objectName": null, "class": "QComboBox",
|
|
"purpose": "the sensor type: which configs exist follows from it" },
|
|
{ "id": "picker_config_label", "path": "sensor_picker.config_label", "objectName": null, "style": "fieldLabel", "class": "QLabel",
|
|
"purpose": "label of the config-file field" },
|
|
{ "id": "picker_config_combo", "path": "sensor_picker.config_combo", "objectName": null, "class": "QComboBox",
|
|
"purpose": "the config file that belongs to the selected sensor type" }
|
|
]
|
|
},
|
|
{
|
|
"id": "sensor_dialog",
|
|
"path": "sensor_dialog",
|
|
"objectName": "sensorDialog",
|
|
"class": "SensorDialog",
|
|
"condition": "sensor_dialog_open",
|
|
"purpose": "the pickers' dialog plus the calibration facts that follow them; non-modal and built on first use",
|
|
"elements": [
|
|
{ "id": "dialog_calibration_title", "path": "sensor_dialog.calib_title", "objectName": null, "style": "panelTitle", "class": "QLabel",
|
|
"purpose": "heading of the calibration half" },
|
|
{ "id": "dialog_version_label", "path": "sensor_dialog.version_label", "objectName": null, "style": "panelMeta", "class": "ShrinkableLabel",
|
|
"purpose": "the calibration identity the device confirmed" },
|
|
{ "id": "dialog_axes_label", "path": "sensor_dialog.axes_label", "objectName": null, "style": "panelMeta", "class": "ShrinkableLabel",
|
|
"purpose": "which axes that calibration covers" },
|
|
{ "id": "dialog_config_label", "path": "sensor_dialog.config_label", "objectName": null, "style": "panelMeta", "class": "ShrinkableLabel",
|
|
"purpose": "the active sensor config file" },
|
|
{ "id": "dialog_reveal", "path": "sensor_dialog.btn_reveal", "objectName": null, "class": "QPushButton",
|
|
"purpose": "show that config file in the file manager" },
|
|
{ "id": "dialog_export_title", "path": "sensor_dialog.export_title", "objectName": null, "style": "panelTitle", "class": "QLabel",
|
|
"purpose": "heading of the exported-coefficients half" },
|
|
{ "id": "dialog_export_label", "path": "sensor_dialog.export_label", "objectName": null, "style": "panelMeta", "class": "ShrinkableLabel",
|
|
"purpose": "the last coefficients file written by the calibration panel" },
|
|
{ "id": "dialog_identity_label", "path": "sensor_dialog.identity_label", "objectName": null, "style": "panelMeta", "class": "ShrinkableLabel",
|
|
"purpose": "what that file says it belongs to, so a mismatched apply is visible" },
|
|
{ "id": "dialog_export", "path": "sensor_dialog.btn_export", "objectName": null, "class": "QPushButton",
|
|
"purpose": "write the calibration panel's coefficients (the panel's own action)" },
|
|
{ "id": "dialog_apply", "path": "sensor_dialog.btn_apply", "objectName": null, "class": "QPushButton",
|
|
"purpose": "push the last export into the running device" },
|
|
{ "id": "dialog_close", "path": "sensor_dialog.btn_close", "objectName": null, "class": "QPushButton",
|
|
"purpose": "close the dialog" }
|
|
]
|
|
}
|
|
],
|
|
"panes": [
|
|
{
|
|
"id": "measurement_2d",
|
|
"path": "arrow_panel",
|
|
"objectName": null,
|
|
"style": "contentPanel",
|
|
"class": "ArrowPanel",
|
|
"views": ["live", "replay"],
|
|
"purpose": "the 2D measurement pane: arrows, stitched arrows or depth, drawn from the same frame the 3D pane reads",
|
|
"elements": [
|
|
{ "id": "arrow_title", "path": "arrow_panel.title", "objectName": null, "style": "panelTitle", "class": "QLabel",
|
|
"purpose": "panel heading" },
|
|
{ "id": "arrow_view_combo", "path": "arrow_panel.view_combo", "objectName": null, "class": "QComboBox",
|
|
"purpose": "the view modes the current frame can actually offer (a recording offers only its raw picture)" },
|
|
{ "id": "arrow_canvas", "path": "arrow_panel.image_label", "objectName": null, "class": "AspectImageLabel",
|
|
"purpose": "the picture area, which keeps the frame's own aspect ratio" },
|
|
{ "id": "arrow_scale_label", "path": "arrow_panel.scale_label", "objectName": null, "style": "hintText", "class": "ShrinkableLabel",
|
|
"purpose": "footer: the magnification the arrows were drawn at" },
|
|
{ "id": "arrow_pipeline_label", "path": "arrow_panel.pipeline_label", "objectName": null, "style": "hintText", "class": "ShrinkableLabel",
|
|
"purpose": "footer: source to output, shown with the diagnostics switch" },
|
|
{ "id": "arrow_area_label", "path": "arrow_panel.area_label", "objectName": null, "style": "hintText", "class": "ShrinkableLabel",
|
|
"purpose": "footer: the valid sensing area of the frame" },
|
|
{ "id": "arrow_note_label", "path": "arrow_panel.note_label", "objectName": null, "style": "hintText", "class": "ShrinkableLabel",
|
|
"purpose": "one line explaining an unexpected plan, e.g. that a recording stores only the raw camera image" },
|
|
{ "id": "arrow_colorbar", "path": "arrow_panel.colorbar", "objectName": "colorBar", "class": "ColorBarLabel",
|
|
"purpose": "depth colour strip, painted from the same lookup table the picture uses; hidden outside the depth view" },
|
|
{ "id": "arrow_depth_scale", "path": "arrow_panel.depth_scale_label", "objectName": null, "style": "panelMeta", "class": "QLabel",
|
|
"purpose": "the fixed depth range the colour strip is scaled to; hidden outside the depth view" }
|
|
]
|
|
},
|
|
{
|
|
"id": "measurement_3d",
|
|
"path": "mesh3d_panel",
|
|
"objectName": null,
|
|
"style": "contentPanel",
|
|
"class": "Mesh3dPanel",
|
|
"views": ["live", "replay"],
|
|
"condition": "mesh3d_view",
|
|
"purpose": "the 3D pane: a peer of the 2D pane and never a replacement for it; only a finger sensor has a scene",
|
|
"elements": [
|
|
{ "id": "mesh3d_title", "path": "mesh3d_panel.title", "objectName": null, "style": "panelTitle", "class": "QLabel",
|
|
"purpose": "panel heading" },
|
|
{ "id": "mesh3d_reset_view", "path": "mesh3d_panel.btn_reset_view", "objectName": null, "class": "QPushButton",
|
|
"purpose": "back to the framing the scene asset calls its own" },
|
|
{ "id": "mesh3d_settings_button", "path": "mesh3d_panel.btn_settings", "objectName": null, "class": "QPushButton",
|
|
"purpose": "open the 3D settings dialog; also what builds the view on first use" },
|
|
{ "id": "mesh3d_message", "path": "mesh3d_panel.message", "objectName": null, "style": "hintText", "class": "QLabel",
|
|
"purpose": "the one-line reason there is no picture (no scene, no asset, no GL, loading, refused)" },
|
|
{ "id": "mesh3d_native_handle", "path": "mesh3d_panel._native_handle", "objectName": "mesh3dNativeHandle", "class": "QWidget",
|
|
"purpose": "zero-sized holder that reserves the window's native handle, so adding the first GL widget cannot make Qt re-create (flash) the window" },
|
|
{ "id": "mesh3d_controls", "path": "mesh3d_panel.view.panel.controls_widget", "objectName": "scene3dControls", "class": "QWidget", "condition": "mesh3d_panel",
|
|
"purpose": "the block of 3D settings: the pane never shows it, the settings dialog hosts it" },
|
|
{ "id": "mesh3d_enable", "path": "mesh3d_panel.view.panel.enable_check", "objectName": null, "class": "QCheckBox", "condition": "mesh3d_panel",
|
|
"purpose": "the pane's own on/off switch, the same state as the view menu's 3D entry" },
|
|
{ "id": "mesh3d_preset_label", "path": "mesh3d_panel.view.panel.preset_label", "objectName": null, "class": "QLabel", "condition": "mesh3d_panel",
|
|
"purpose": "label of the camera-preset field" },
|
|
{ "id": "mesh3d_preset_combo", "path": "mesh3d_panel.view.panel.preset_combo", "objectName": null, "class": "QComboBox", "condition": "mesh3d_panel",
|
|
"purpose": "camera presets: the asset's own default view and the ones it names" },
|
|
{ "id": "mesh3d_housing", "path": "mesh3d_panel.view.panel.housing_check", "objectName": null, "class": "QCheckBox", "condition": "mesh3d_panel",
|
|
"purpose": "show the housing geometry, a mechanical check rather than the measurement; off by default" },
|
|
{ "id": "mesh3d_deform", "path": "mesh3d_panel.view.panel.deform_check", "objectName": null, "class": "QCheckBox", "condition": "mesh3d_panel",
|
|
"purpose": "exaggerate the deformation so a small displacement is visible" },
|
|
{ "id": "mesh3d_gain", "path": "mesh3d_panel.view.panel.gain_spin", "objectName": null, "class": "QDoubleSpinBox", "condition": "mesh3d_panel",
|
|
"purpose": "the deformation exaggeration factor" },
|
|
{ "id": "mesh3d_field_label", "path": "mesh3d_panel.view.panel.field_label", "objectName": null, "class": "QLabel", "condition": "mesh3d_panel",
|
|
"purpose": "label of the displayed-quantity field" },
|
|
{ "id": "mesh3d_field_combo", "path": "mesh3d_panel.view.panel.field_combo", "objectName": null, "class": "QComboBox", "condition": "mesh3d_panel",
|
|
"purpose": "which quantity colours the surface: depth response or optical-flow magnitude" },
|
|
{ "id": "mesh3d_field_note", "path": "mesh3d_panel.view.panel.field_note", "objectName": null, "style": "hintText", "class": "QLabel", "condition": "mesh3d_panel",
|
|
"purpose": "says which quantity is actually on screen" },
|
|
{ "id": "mesh3d_flow_arrows", "path": "mesh3d_panel.view.panel.flow_arrows_check", "objectName": null, "class": "QCheckBox", "condition": "mesh3d_panel",
|
|
"purpose": "the optical-flow arrow overlay on the surface" },
|
|
{ "id": "mesh3d_contact_arrow", "path": "mesh3d_panel.view.panel.contact_arrow_check", "objectName": null, "class": "QCheckBox", "condition": "mesh3d_panel",
|
|
"purpose": "the contact arrow overlay: where the surface is being touched" },
|
|
{ "id": "mesh3d_scale_label", "path": "mesh3d_panel.view.panel.scale_label", "objectName": null, "class": "QLabel", "condition": "mesh3d_panel",
|
|
"purpose": "label of the colour-scale mode field" },
|
|
{ "id": "mesh3d_scale_combo", "path": "mesh3d_panel.view.panel.scale_combo", "objectName": null, "class": "QComboBox", "condition": "mesh3d_panel",
|
|
"purpose": "fixed bounds or an automatic range for the colour scale" },
|
|
{ "id": "mesh3d_range_label", "path": "mesh3d_panel.view.panel.range_label", "objectName": null, "class": "QLabel", "condition": "mesh3d_panel",
|
|
"purpose": "label of the fixed colour-range fields" },
|
|
{ "id": "mesh3d_range_lower", "path": "mesh3d_panel.view.panel.lower_spin", "objectName": null, "class": "QDoubleSpinBox", "condition": "mesh3d_panel",
|
|
"purpose": "lower bound of the fixed colour range" },
|
|
{ "id": "mesh3d_range_upper", "path": "mesh3d_panel.view.panel.upper_spin", "objectName": null, "class": "QDoubleSpinBox", "condition": "mesh3d_panel",
|
|
"purpose": "upper bound of the fixed colour range" },
|
|
{ "id": "mesh3d_fps_label", "path": "mesh3d_panel.view.panel.fps_label", "objectName": null, "class": "QLabel", "condition": "mesh3d_panel",
|
|
"purpose": "label of the display-budget field" },
|
|
{ "id": "mesh3d_fps_combo", "path": "mesh3d_panel.view.panel.fps_combo", "objectName": null, "class": "QComboBox", "condition": "mesh3d_panel",
|
|
"purpose": "the 3D pane's display budget in frames per second; never reaches the camera or the recording" },
|
|
{ "id": "mesh3d_reset_button", "path": "mesh3d_panel.view.panel.reset_button", "objectName": null, "class": "QPushButton", "condition": "mesh3d_panel",
|
|
"purpose": "reset the 3D view from inside the settings dialog" },
|
|
{ "id": "mesh3d_stats", "path": "mesh3d_panel.view.panel.stats_label", "objectName": null, "style": "panelMeta", "class": "QLabel", "condition": "mesh3d_panel",
|
|
"purpose": "display-side counters: an engineering readout, not something to read while measuring" },
|
|
{ "id": "mesh3d_scale_lower", "path": "mesh3d_panel.view.panel.scale_lower_label", "objectName": null, "style": "panelMeta", "class": "QLabel", "condition": "mesh3d_panel",
|
|
"purpose": "the lower bound the legend was drawn with" },
|
|
{ "id": "mesh3d_scale_bar", "path": "mesh3d_panel.view.panel.scale_bar", "objectName": "colorScaleBar", "class": "ColorScaleBar", "condition": "mesh3d_panel",
|
|
"purpose": "the colour ramp the shader actually used, from the same lookup table" },
|
|
{ "id": "mesh3d_scale_upper", "path": "mesh3d_panel.view.panel.scale_upper_label", "objectName": null, "style": "panelMeta", "class": "QLabel", "condition": "mesh3d_panel",
|
|
"purpose": "the upper bound the legend was drawn with" },
|
|
{ "id": "mesh3d_legend", "path": "mesh3d_panel.view.panel.legend_label", "objectName": null, "style": "panelMeta", "class": "ShrinkableLabel", "condition": "mesh3d_panel",
|
|
"purpose": "one legend line: what the colours mean" },
|
|
{ "id": "mesh3d_source", "path": "mesh3d_panel.view.panel.source_label", "objectName": null, "style": "panelMeta", "class": "ShrinkableLabel", "condition": "mesh3d_panel",
|
|
"purpose": "which source produced the field on screen: live, replay or a recompute pass" },
|
|
{ "id": "mesh3d_state", "path": "mesh3d_panel.view.panel.state_label", "objectName": null, "style": "hintText", "class": "QLabel", "condition": "mesh3d_panel",
|
|
"purpose": "the scene state line: whether this frame is a measurement or a reference" }
|
|
]
|
|
},
|
|
{
|
|
"id": "charts",
|
|
"path": "force_plots",
|
|
"objectName": null,
|
|
"style": "contentPanel",
|
|
"class": "ForcePlotsPanel",
|
|
"views": ["live", "replay", "calibration"],
|
|
"purpose": "the force charts: three channels plus a whole-session overview strip, with the per-axis readings above the curves",
|
|
"rows": {
|
|
"values": [0, 1, 2],
|
|
"names": ["fn", "fx", "fy"],
|
|
"keys": ["card", "badge", "name", "value", "unit", "detail"],
|
|
"template": {
|
|
"card": { "id": "chart_{name}_card", "path": "force_plots.value_cards[{i}]", "objectName": null, "style": "valueGroup", "class": "QFrame",
|
|
"purpose": "the {name} channel's reading: badge and name over the number, then the selection's mean and sigma" },
|
|
"badge": { "id": "chart_{name}_badge", "path": "force_plots.channel_badges[{i}]", "objectName": null, "style": "unitBadge", "class": "QLabel",
|
|
"purpose": "the {name} channel badge, coloured like its curve" },
|
|
"name": { "id": "chart_{name}_name", "path": "force_plots.channel_names[{i}]", "objectName": null, "style": "hintText", "class": "QLabel",
|
|
"purpose": "what the {name} channel measures, in the operator's language" },
|
|
"value": { "id": "chart_{name}_value", "path": "force_plots.value_labels[{i}]", "objectName": null, "style": "valueCard", "class": "QLabel",
|
|
"purpose": "the {name} reading itself: large monospace digits, so the axes can be compared at a glance" },
|
|
"unit": { "id": "chart_{name}_unit", "path": "force_plots.value_units[{i}]", "objectName": null, "style": "hintText", "class": "QLabel",
|
|
"purpose": "the unit the {name} reading is in, which the chip decides for every axis at once" },
|
|
"detail": { "id": "chart_{name}_detail", "path": "force_plots.value_details[{i}]", "objectName": null, "style": "hintText", "class": "QLabel",
|
|
"purpose": "the {name} selection's mean and sigma; kept at a fixed height, so selecting a window adds a number instead of moving the charts" }
|
|
}
|
|
},
|
|
"elements": [
|
|
{ "id": "chart_title", "path": "force_plots.title", "objectName": null, "style": "panelTitle", "class": "QLabel",
|
|
"purpose": "panel heading" },
|
|
{ "id": "chart_mode_label", "path": "force_plots.mode_label", "objectName": null, "style": "hintText", "class": "QLabel",
|
|
"purpose": "whether the charts are following live, browsing a selection, or reading a recording" },
|
|
{ "id": "chart_latest", "path": "force_plots.btn_latest", "objectName": null, "class": "QPushButton",
|
|
"purpose": "leave browsing and follow the newest sample; disabled for a recording" },
|
|
{ "id": "chart_overview", "path": "force_plots.btn_overview", "objectName": null, "class": "QPushButton",
|
|
"purpose": "fit the whole session in the window" },
|
|
{ "id": "chart_box_zoom", "path": "force_plots.btn_box_zoom", "objectName": null, "class": "QPushButton",
|
|
"purpose": "box zoom, the one chart action that is a mode and stays checked" },
|
|
{ "id": "chart_settings", "path": "force_plots.btn_settings", "objectName": null, "class": "QPushButton",
|
|
"purpose": "open the chart settings: window, ranges and acquisition filters" },
|
|
{ "id": "chart_unit_badge", "path": "force_plots.unit_badge", "objectName": null, "style": "unitBadge", "class": "QPushButton",
|
|
"purpose": "the unit chip: a button, because switching newtons/raw is a chart concern" },
|
|
{ "id": "chart_value_group", "path": "force_plots.value_group", "objectName": null, "style": "valueGroup", "class": "QFrame",
|
|
"purpose": "the row of per-axis readings, placed above the curves because that is what is read at a glance" },
|
|
{ "id": "chart_plot_frame", "path": "force_plots.plot_frame", "objectName": null, "style": "cardPanel", "class": "QFrame",
|
|
"purpose": "the raised container of the plot canvas" },
|
|
{ "id": "chart_plot_widget", "path": "force_plots.plot_widget", "objectName": null, "class": "GraphicsLayoutWidget",
|
|
"purpose": "the pyqtgraph canvas: one subplot per channel and the whole-session overview strip below them" },
|
|
{ "id": "chart_window_caption", "path": "force_plots.window_caption", "objectName": null, "style": "panelTitle", "class": "QLabel",
|
|
"purpose": "caption of the overview strip" },
|
|
{ "id": "chart_window_label", "path": "force_plots.window_label", "objectName": null, "style": "panelMeta", "class": "QLabel",
|
|
"purpose": "the time window the strip is showing" },
|
|
{ "id": "chart_stats", "path": "force_plots.stats_label", "objectName": null, "style": "panelMeta", "class": "QLabel",
|
|
"purpose": "how long the selection is and how many samples it holds; the full per-axis readout is its tooltip" }
|
|
]
|
|
},
|
|
{
|
|
"id": "cal_panel",
|
|
"path": "cal_panel",
|
|
"objectName": null,
|
|
"style": "contentPanel",
|
|
"class": "CalPanel",
|
|
"views": ["calibration"],
|
|
"purpose": "the calibration panel: sample each axis against a reference load, review the points, export and apply the coefficients",
|
|
"elements": [
|
|
{ "id": "cal_scroll", "path": "cal_panel.scroll", "objectName": null, "class": "QScrollArea",
|
|
"purpose": "everything below the title scrolls, so a short window clips content instead of losing a function" },
|
|
{ "id": "cal_title", "path": "cal_panel.title", "objectName": null, "style": "panelTitle", "class": "QLabel",
|
|
"purpose": "panel heading" },
|
|
{ "id": "cal_channel_badge", "path": "cal_panel.channel_badge", "objectName": null, "style": "unitBadge", "class": "QLabel",
|
|
"purpose": "which channel is being calibrated, coloured like that channel's curve" },
|
|
{ "id": "cal_radio_fn", "path": "cal_panel.radio_fn", "objectName": null, "class": "QRadioButton",
|
|
"purpose": "calibrate the normal force channel" },
|
|
{ "id": "cal_radio_fx", "path": "cal_panel.radio_fx", "objectName": null, "class": "QRadioButton",
|
|
"purpose": "calibrate the shear channel FX" },
|
|
{ "id": "cal_radio_fy", "path": "cal_panel.radio_fy", "objectName": null, "class": "QRadioButton",
|
|
"purpose": "calibrate the shear channel FY" },
|
|
{ "id": "cal_weight_label", "path": "cal_panel._weight_label", "objectName": null, "style": "fieldLabel", "class": "QLabel",
|
|
"purpose": "label of the reference-load field" },
|
|
{ "id": "cal_weight_spin", "path": "cal_panel.weight_spin", "objectName": null, "class": "QDoubleSpinBox",
|
|
"purpose": "the reference load applied for the point being sampled" },
|
|
{ "id": "cal_duration_label", "path": "cal_panel._duration_label", "objectName": null, "style": "fieldLabel", "class": "QLabel",
|
|
"purpose": "label of the sampling-duration field" },
|
|
{ "id": "cal_duration_spin", "path": "cal_panel.duration_spin", "objectName": null, "class": "QDoubleSpinBox",
|
|
"purpose": "how long one calibration point is sampled for" },
|
|
{ "id": "cal_record", "path": "cal_panel.btn_record", "objectName": null, "style": "primaryButton / dangerButton", "class": "QPushButton",
|
|
"purpose": "sample one point; while sampling it is the cancel affordance and stays enabled" },
|
|
{ "id": "cal_progress", "path": "cal_panel.progress", "objectName": null, "class": "QProgressBar",
|
|
"purpose": "how far the current point's sampling has got" },
|
|
{ "id": "cal_live_label", "path": "cal_panel.live_label", "objectName": null, "class": "QLabel",
|
|
"purpose": "the three raw axis readings while a point is sampled" },
|
|
{ "id": "cal_empty_hint", "path": "cal_panel.empty_hint", "objectName": null, "style": "hintText", "class": "QLabel",
|
|
"purpose": "what to do when there are no points yet" },
|
|
{ "id": "cal_table", "path": "cal_panel.table", "objectName": null, "class": "QTableWidget", "attrs": { "columnCount": 6 },
|
|
"purpose": "the diagnostic table: six columns (reference load, FN, FX, FY, stability, state), resized to their contents so a narrow panel scrolls instead of eliding a heading" },
|
|
{ "id": "cal_fit_label", "path": "cal_panel.fit_label", "objectName": null, "style": "hintText", "class": "QLabel",
|
|
"purpose": "what the current points fit: the readout that says whether more points are needed" },
|
|
{ "id": "cal_sn_label", "path": "cal_panel._sn_label", "objectName": null, "style": "fieldLabel", "class": "QLabel",
|
|
"purpose": "label of the device serial field" },
|
|
{ "id": "cal_sn_edit", "path": "cal_panel.sn_edit", "objectName": null, "class": "QLineEdit",
|
|
"purpose": "the device serial written into the exported coefficients and the session metadata" },
|
|
{ "id": "cal_toggle_excluded", "path": "cal_panel.btn_toggle_excluded", "objectName": null, "class": "QPushButton",
|
|
"purpose": "selection action: exclude or include the selected point in the fit" },
|
|
{ "id": "cal_toggle_validation", "path": "cal_panel.btn_toggle_validation", "objectName": null, "class": "QPushButton",
|
|
"purpose": "selection action: mark the selected point as a validation point" },
|
|
{ "id": "cal_delete", "path": "cal_panel.btn_delete", "objectName": null, "class": "QPushButton",
|
|
"purpose": "selection action: delete the selected point" },
|
|
{ "id": "cal_clear_channel", "path": "cal_panel.btn_clear_channel", "objectName": null, "class": "QPushButton",
|
|
"purpose": "selection action: clear the channel being calibrated" },
|
|
{ "id": "cal_clear_all", "path": "cal_panel.btn_clear_all", "objectName": null, "class": "QPushButton",
|
|
"purpose": "selection action: clear every channel" },
|
|
{ "id": "cal_open_record", "path": "cal_panel.btn_open_record", "objectName": null, "class": "QPushButton",
|
|
"purpose": "file row: load a saved calibration record" },
|
|
{ "id": "cal_save_record", "path": "cal_panel.btn_save_record", "objectName": null, "class": "QPushButton",
|
|
"purpose": "file row: save the samples and their identity as an audit record" },
|
|
{ "id": "cal_export", "path": "cal_panel.btn_export", "objectName": null, "class": "QPushButton",
|
|
"purpose": "file row: write the fitted coefficients to disk; it does not apply them" },
|
|
{ "id": "cal_apply", "path": "cal_panel.btn_apply", "objectName": null, "style": "primaryButton", "class": "QPushButton",
|
|
"purpose": "file row: push the last export into the running device" }
|
|
]
|
|
}
|
|
]
|
|
}
|