generated from hulk/gd32f4xx_template_cmake_vscode
- TemperatureChart gains a right-edge slider on a FIXED -20..120C rail (user-specified): top handle = upper bound, bottom handle = lower bound, drag middle band = pan keeping span, click free rail = snap that bound. First drag engages manual mode (yManualEngaged -> sync button state + config); release quantizes to 0.1C via yRangeChanged, min span 0.5C. Bound values during drag must come from live display range, not the stale paint cache (two drags without repaint made pan jump the range). - Auto/manual toggle moved into the card header (was a wrapped second row); pause/clear merged into header too; legend moved to top-left. - Value labels next to handles only visible on hover/drag (constant labels covered the curve per user feedback). - Out-of-range curves clipped back to the plot area (setClipRect); current-value marker suppressed outside the manual range. - Draft textbox rows from v0.2.10 superseded; versions v0.2.10..v0.2.13. - Fixed "no data yet" hint never disappearing: set_source cached a stale _has_data bool once at build time while data flows through the same list references; now evaluated live at paint time. - config: y_axis key persists mode/bounds; invalid stored values fall back to auto. selftest settings isolated to a temp dir (user json with stored manual state broke the fresh-default assertions). - selftest_gui: slider hit/drag/clamp/pan/clipping regression + hint gating; 83 checks all pass.
103 lines
3.6 KiB
Python
103 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
工具运行配置: 运行目录定位 / filmforge_gui.json(串口记忆 + 监视设置)
|
|
================================================================
|
|
PyInstaller onefile 打包后 __file__ 指向临时解压目录,
|
|
配置必须落在 exe 旁边, 故 frozen 时改用 sys.executable 定位。
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
import filmforge_rs485
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
BASE_DIR = os.path.dirname(os.path.abspath(sys.executable))
|
|
else:
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
SETTINGS_FILE = os.path.join(BASE_DIR, 'filmforge_gui.json')
|
|
LOG_DIR = os.path.join(BASE_DIR, 'logs')
|
|
|
|
# 曲线时间窗候选(秒): 下拉框文案 -> 秒
|
|
WINDOW_OPTIONS = (('2 分钟', 120), ('5 分钟', 300), ('15 分钟', 900),
|
|
('60 分钟', 3600), ('全部', 0))
|
|
DEFAULT_WINDOW_SEC = 300
|
|
MAX_SAMPLES = 200000 # 曲线内存上限(点), 超出丢最旧; 10Hz 下约 5.5 小时
|
|
|
|
# 串口记忆(协议固定 115200-8-N-1, 仍允许改以便调试)
|
|
SERIAL_CFG = {'port': '', 'baud': filmforge_rs485.DEFAULT_BAUD,
|
|
'bytesize': 8, 'parity': 'N', 'stopbits': 1}
|
|
REPORT_MS = 500 # 上报间隔输入框默认值
|
|
WINDOW_SEC = DEFAULT_WINDOW_SEC
|
|
THEME = 'System'
|
|
Y_MODE = 'auto' # 曲线 Y 轴(温度轴): 'auto'=自适应 / 'manual'=固定范围
|
|
Y_MIN = None # 手动 Y 轴上下限(℃), 仅 Y_MODE='manual' 且合法时生效
|
|
Y_MAX = None
|
|
|
|
|
|
def load_settings():
|
|
"""读 filmforge_gui.json(缺失/损坏按默认值), 更新模块级默认字典"""
|
|
try:
|
|
with open(SETTINGS_FILE, encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
except Exception:
|
|
return
|
|
if not isinstance(data, dict):
|
|
return
|
|
ser = data.get('serial')
|
|
if isinstance(ser, dict):
|
|
SERIAL_CFG['port'] = str(ser.get('port') or '')
|
|
try:
|
|
SERIAL_CFG['baud'] = int(ser.get('baud') or filmforge_rs485.DEFAULT_BAUD)
|
|
except (TypeError, ValueError):
|
|
pass
|
|
try:
|
|
SERIAL_CFG['bytesize'] = int(ser.get('bytesize') or 8)
|
|
except (TypeError, ValueError):
|
|
pass
|
|
SERIAL_CFG['parity'] = str(ser.get('parity') or 'N').upper()[:1]
|
|
try:
|
|
SERIAL_CFG['stopbits'] = int(ser.get('stopbits') or 1)
|
|
except (TypeError, ValueError):
|
|
pass
|
|
for key, target in (('report_ms', 'REPORT_MS'), ('window_sec', 'WINDOW_SEC')):
|
|
try:
|
|
globals()[target] = int(data.get(key))
|
|
except (TypeError, ValueError):
|
|
pass
|
|
global THEME
|
|
theme = str(data.get('theme') or '')
|
|
if theme in ('Dark', 'Light', 'System'):
|
|
THEME = theme
|
|
ax = data.get('y_axis')
|
|
if isinstance(ax, dict):
|
|
mode = str(ax.get('mode') or '')
|
|
if mode in ('auto', 'manual'):
|
|
globals()['Y_MODE'] = mode
|
|
try:
|
|
globals()['Y_MIN'] = round(float(ax.get('min')), 2)
|
|
except (TypeError, ValueError):
|
|
pass
|
|
try:
|
|
globals()['Y_MAX'] = round(float(ax.get('max')), 2)
|
|
except (TypeError, ValueError):
|
|
pass
|
|
|
|
|
|
def save_settings():
|
|
"""写出全部设置(始终写全键)"""
|
|
data = {
|
|
'serial': dict(SERIAL_CFG),
|
|
'report_ms': REPORT_MS,
|
|
'window_sec': WINDOW_SEC,
|
|
'theme': THEME,
|
|
'y_axis': {'mode': Y_MODE, 'min': Y_MIN, 'max': Y_MAX},
|
|
}
|
|
try:
|
|
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
except OSError:
|
|
pass # 只读目录(exe 旁)下静默放弃, 不影响运行
|