{ "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..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" } ] } ] }