设置缩放

This commit is contained in:
i
2026-04-22 10:36:05 +08:00
parent 9132312f83
commit e06297f06b
+191 -45
View File
@@ -46,7 +46,7 @@ from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QLineEdit, QCheckBox, QTabWidget,
QComboBox, QFileDialog, QTextEdit, QFrame, QMessageBox,
QSizePolicy, QGroupBox,
QSizePolicy, QGroupBox, QDialog, QAction,
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
from PyQt5.QtGui import QImage, QPixmap, QFont
@@ -58,6 +58,128 @@ PARAMS_FILE = "camera_params.json"
MIN_DEPTH_MM, MAX_DEPTH_MM = 200, 5000
_DISCONNECT_TIMEOUTS = 6 # 6 × 500 ms = 3 s 无帧 → 断线
_UI_SCALE: float = 1.0 # initialized in main() before window creation
def _fs(n: float) -> int:
"""Scale a font-size (px or pt) value."""
return max(8, int(n * _UI_SCALE))
def _sz(n: float) -> int:
"""Scale a UI dimension (px)."""
return max(1, int(n * _UI_SCALE))
# ─────────────────────────────────────────────────────────────────
# App Settings
# ─────────────────────────────────────────────────────────────────
class AppSettings:
_CONFIG_PATH = Path(__file__).parent / "app_settings.json"
FONT_SCALE_AUTO = "auto"
def __init__(self):
self.font_scale: str = self.FONT_SCALE_AUTO
self._load()
def _load(self):
if self._CONFIG_PATH.is_file():
try:
data = json.loads(self._CONFIG_PATH.read_text(encoding="utf-8"))
self.font_scale = data.get("font_scale", self.FONT_SCALE_AUTO)
except Exception:
pass
def save(self):
self._CONFIG_PATH.write_text(
json.dumps({"font_scale": self.font_scale}, ensure_ascii=False, indent=2),
encoding="utf-8",
)
@staticmethod
def compute_auto_scale() -> float:
screen = QApplication.primaryScreen()
if screen:
dpr = screen.devicePixelRatio()
return round(max(0.8, min(2.0, dpr)), 2)
return 1.0
def get_scale_factor(self) -> float:
if self.font_scale == self.FONT_SCALE_AUTO:
return self.compute_auto_scale()
try:
return float(self.font_scale)
except ValueError:
return 1.0
# ─────────────────────────────────────────────────────────────────
# Settings Dialog
# ─────────────────────────────────────────────────────────────────
class SettingsDialog(QDialog):
def __init__(self, settings: "AppSettings", parent=None):
super().__init__(parent)
self.setWindowTitle("程序设置")
self.setFixedSize(_sz(420), _sz(160))
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
self._settings = settings
self._build_ui()
def _build_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(_sz(16), _sz(14), _sz(16), _sz(14))
layout.setSpacing(_sz(10))
row = QHBoxLayout()
lbl = QLabel("字体缩放比例:")
lbl.setMinimumWidth(_sz(110))
row.addWidget(lbl)
self._scale_combo = QComboBox()
self._scale_combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
for key, label in [
("auto", "自动(根据屏幕分辨率和缩放比例)"),
("0.8", "0.8× 小"),
("1.0", "1.0× 标准"),
("1.2", "1.2× 稍大"),
("1.5", "1.5× 大"),
("2.0", "2.0× 特大"),
]:
self._scale_combo.addItem(label, key)
idx = self._scale_combo.findData(self._settings.font_scale)
if idx >= 0:
self._scale_combo.setCurrentIndex(idx)
row.addWidget(self._scale_combo)
row.addStretch()
layout.addLayout(row)
auto_val = AppSettings.compute_auto_scale()
info = QLabel(f"修改后需重启程序生效。当前屏幕自动比例:×{auto_val:.2f}")
info.setStyleSheet(f"color:#888; font-size:{_fs(11)}px;")
layout.addWidget(info)
layout.addStretch()
btn_row = QHBoxLayout()
btn_row.addStretch()
ok_btn = QPushButton("确定")
ok_btn.setFixedWidth(_sz(80))
ok_btn.clicked.connect(self._save_and_close)
cancel_btn = QPushButton("取消")
cancel_btn.setFixedWidth(_sz(80))
cancel_btn.clicked.connect(self.reject)
btn_row.addWidget(ok_btn)
btn_row.addWidget(cancel_btn)
layout.addLayout(btn_row)
def _save_and_close(self):
self._settings.font_scale = self._scale_combo.currentData()
self._settings.save()
self.accept()
# ─────────────────────────────────────────────────────────────────
# Helpers
@@ -675,11 +797,11 @@ class _Toast(QLabel):
self.setAlignment(Qt.AlignCenter)
self.setAttribute(Qt.WA_TransparentForMouseEvents, True)
self.setStyleSheet(
"background: rgba(30,140,30,210);"
"color: white;"
"border-radius: 10px;"
"font-size: 15px;"
"padding: 10px 28px;"
f"background: rgba(30,140,30,210);"
f"color: white;"
f"border-radius: {_sz(10)}px;"
f"font-size: {_fs(15)}px;"
f"padding: {_sz(10)}px {_sz(28)}px;"
)
self._timer = QTimer(self)
self._timer.setSingleShot(True)
@@ -691,7 +813,7 @@ class _Toast(QLabel):
self.adjustSize()
p = self.parent()
self.move((p.width() - self.width()) // 2,
p.height() - self.height() - 70)
p.height() - self.height() - _sz(70))
self.raise_()
self.show()
self._timer.start(ms)
@@ -700,7 +822,7 @@ class _Toast(QLabel):
if self.isVisible():
p = self.parent()
self.move((p.width() - self.width()) // 2,
p.height() - self.height() - 70)
p.height() - self.height() - _sz(70))
# ─────────────────────────────────────────────────────────────────
@@ -714,7 +836,7 @@ class MainWindow(QMainWindow):
if _COPYRIGHT:
_title += f" | © {_COPYRIGHT}"
self.setWindowTitle(_title)
self.resize(1300, 860)
self.resize(_sz(1300), _sz(860))
self._worker: CameraWorker | None = None
self._batch_worker: BatchD2CWorker | None = None
@@ -723,6 +845,7 @@ class MainWindow(QMainWindow):
self._params_filename = "camera_params.json"
self._color_profiles_data: list[dict] = []
self._depth_profiles_data: list[dict] = []
self._app_settings = AppSettings()
self._build_ui()
# toast 覆盖在 central widget 上
@@ -733,6 +856,12 @@ class MainWindow(QMainWindow):
# ─────────────────────────────────
def _build_ui(self):
# 菜单栏
settings_menu = self.menuBar().addMenu("设置")
action_pref = QAction("程序设置…", self)
action_pref.triggered.connect(self._show_settings)
settings_menu.addAction(action_pref)
central = QWidget()
self.setCentralWidget(central)
root = QVBoxLayout(central)
@@ -742,13 +871,13 @@ class MainWindow(QMainWindow):
# 版权信息栏(右上角)
if _COPYRIGHT:
cr_bar = QWidget()
cr_bar.setFixedHeight(22)
cr_bar.setFixedHeight(_sz(22))
cr_bar.setStyleSheet("background: #222;")
cr_layout = QHBoxLayout(cr_bar)
cr_layout.setContentsMargins(8, 0, 10, 0)
cr_layout.addStretch()
cr_lbl = QLabel(f"© {_COPYRIGHT}")
cr_lbl.setStyleSheet("color: #888; font-size: 11px;")
cr_lbl.setStyleSheet(f"color: #888; font-size:{_fs(11)}px;")
cr_layout.addWidget(cr_lbl)
root.addWidget(cr_bar)
@@ -757,13 +886,17 @@ class MainWindow(QMainWindow):
tabs.addTab(self._build_d2c_tab(), " Orbbec Viewer 深度图 D2C ")
root.addWidget(tabs)
def _show_settings(self):
dlg = SettingsDialog(self._app_settings, self)
dlg.exec_()
@staticmethod
def _step_group(num: int, title: str) -> QGroupBox:
grp = QGroupBox(f"第 {num} 步 · {title}")
grp.setStyleSheet(
"QGroupBox { font-weight:bold; font-size:12px; color:#8ec8ff;"
" border:1px solid #444; border-radius:5px; margin-top:6px; padding-top:4px; }"
"QGroupBox::title { subcontrol-origin:margin; left:10px; padding:0 4px; }"
f"QGroupBox {{ font-weight:bold; font-size:{_fs(12)}px; color:#8ec8ff;"
f" border:1px solid #444; border-radius:5px; margin-top:6px; padding-top:4px; }}"
f"QGroupBox::title {{ subcontrol-origin:margin; left:10px; padding:0 4px; }}"
)
return grp
@@ -780,11 +913,11 @@ class MainWindow(QMainWindow):
g1 = QHBoxLayout(grp1)
g1.setContentsMargins(8, 4, 8, 6)
refresh_btn = QPushButton("刷新设备")
refresh_btn.setFixedHeight(28)
refresh_btn.setFixedHeight(_sz(28))
refresh_btn.clicked.connect(self._refresh_devices)
g1.addWidget(refresh_btn)
self.device_status = QLabel("未连接设备,请点击「刷新设备」")
self.device_status.setStyleSheet("color:#aaa; font-size:12px;")
self.device_status.setStyleSheet(f"color:#aaa; font-size:{_fs(12)}px;")
g1.addWidget(self.device_status)
g1.addStretch()
layout.addWidget(grp1)
@@ -797,23 +930,26 @@ class MainWindow(QMainWindow):
g2.addWidget(QLabel("RGB:"))
self.color_combo = QComboBox()
self.color_combo.setMinimumWidth(250)
self.color_combo.setMaxVisibleItems(12)
self.color_combo.setMinimumWidth(_sz(280))
self.color_combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self.color_combo.setMaxVisibleItems(15)
self.color_combo.setStyleSheet(COMBO_CSS)
g2.addWidget(self.color_combo)
g2.addSpacing(12)
g2.addWidget(QLabel("深度:"))
self.depth_combo = QComboBox()
self.depth_combo.setMinimumWidth(250)
self.depth_combo.setMaxVisibleItems(12)
self.depth_combo.setMinimumWidth(_sz(280))
self.depth_combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self.depth_combo.setMaxVisibleItems(15)
self.depth_combo.setStyleSheet(COMBO_CSS)
g2.addWidget(self.depth_combo)
g2.addSpacing(12)
g2.addWidget(QLabel("Preset:"))
self.preset_combo = QComboBox()
self.preset_combo.setMinimumWidth(160)
self.preset_combo.setMinimumWidth(_sz(180))
self.preset_combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self.preset_combo.setMaxVisibleItems(12)
self.preset_combo.setStyleSheet(COMBO_CSS)
self.preset_combo.setEnabled(False)
@@ -822,7 +958,7 @@ class MainWindow(QMainWindow):
g2.addStretch()
self.stream_btn = QPushButton("开启")
self.stream_btn.setCheckable(True)
self.stream_btn.setFixedSize(88, 30)
self.stream_btn.setFixedSize(_sz(88), _sz(30))
self.stream_btn.setStyleSheet(
"QPushButton{ border-radius:5px; border:none; font-weight:bold; }"
"QPushButton:!checked{ background:#555; color:#ccc; }"
@@ -841,16 +977,16 @@ class MainWindow(QMainWindow):
today = datetime.now()
self.session_edit = QLineEdit(f"{today.year}年{today.month}月{today.day}日")
self.session_edit.setMinimumWidth(160)
self.session_edit.setMinimumWidth(_sz(160))
g3.addWidget(self.session_edit)
create_btn = QPushButton("创建 / 载入")
create_btn.setFixedHeight(28)
create_btn.setFixedHeight(_sz(28))
create_btn.clicked.connect(self._create_session)
g3.addWidget(create_btn)
self.session_status = QLabel("(未创建)")
self.session_status.setStyleSheet("color:gray; font-size:12px;")
self.session_status.setStyleSheet(f"color:gray; font-size:{_fs(12)}px;")
g3.addWidget(self.session_status)
g3.addStretch()
@@ -873,11 +1009,11 @@ class MainWindow(QMainWindow):
box = QVBoxLayout()
hdr = QLabel(title)
hdr.setAlignment(Qt.AlignCenter)
hdr.setStyleSheet("font-weight:bold; font-size:13px;")
hdr.setStyleSheet(f"font-weight:bold; font-size:{_fs(13)}px;")
box.addWidget(hdr)
lbl = QLabel("等待画面…")
lbl.setAlignment(Qt.AlignCenter)
lbl.setMinimumSize(540, 360)
lbl.setMinimumSize(_sz(540), _sz(360))
lbl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
lbl.setStyleSheet("background:#111; border:1px solid #555; color:#666;")
setattr(self, attr, lbl)
@@ -890,18 +1026,18 @@ class MainWindow(QMainWindow):
step4_row = QHBoxLayout()
step4_lbl = QLabel("第 4 步 · 拍照")
step4_lbl.setStyleSheet(
"color:#8ec8ff; font-weight:bold; font-size:12px;")
f"color:#8ec8ff; font-weight:bold; font-size:{_fs(12)}px;")
step4_row.addWidget(step4_lbl)
step4_row.addStretch()
hint = QLabel("快捷键 S")
hint.setStyleSheet("color:#666; font-size:11px;")
hint.setStyleSheet(f"color:#666; font-size:{_fs(11)}px;")
step4_row.addWidget(hint)
self.capture_btn = QPushButton("拍照")
self.capture_btn.setFixedSize(160, 44)
self.capture_btn.setFixedSize(_sz(160), _sz(44))
self.capture_btn.setEnabled(False)
f = QFont(); f.setPointSize(13); f.setBold(True)
f = QFont(); f.setPointSize(_fs(13)); f.setBold(True)
self.capture_btn.setFont(f)
self.capture_btn.setStyleSheet(
"QPushButton{ background:#2a6ebb; color:white; border-radius:8px; border:none; }"
@@ -928,11 +1064,11 @@ class MainWindow(QMainWindow):
g1 = QHBoxLayout(grp1)
g1.setContentsMargins(8, 4, 8, 6)
d2c_refresh_btn = QPushButton("刷新设备")
d2c_refresh_btn.setFixedHeight(28)
d2c_refresh_btn.setFixedHeight(_sz(28))
d2c_refresh_btn.clicked.connect(self._refresh_devices)
g1.addWidget(d2c_refresh_btn)
self.d2c_device_status = QLabel("未连接设备,请点击「刷新设备」")
self.d2c_device_status.setStyleSheet("color:#aaa; font-size:12px;")
self.d2c_device_status.setStyleSheet(f"color:#aaa; font-size:{_fs(12)}px;")
g1.addWidget(self.d2c_device_status)
g1.addStretch()
layout.addWidget(grp1)
@@ -946,22 +1082,24 @@ class MainWindow(QMainWindow):
combo_row = QHBoxLayout()
combo_row.addWidget(QLabel("RGB:"))
self.d2c_color_combo = QComboBox()
self.d2c_color_combo.setMinimumWidth(240)
self.d2c_color_combo.setMaxVisibleItems(12)
self.d2c_color_combo.setMinimumWidth(_sz(280))
self.d2c_color_combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self.d2c_color_combo.setMaxVisibleItems(15)
self.d2c_color_combo.setStyleSheet(COMBO_CSS)
combo_row.addWidget(self.d2c_color_combo)
combo_row.addSpacing(12)
combo_row.addWidget(QLabel("深度:"))
self.d2c_depth_combo = QComboBox()
self.d2c_depth_combo.setMinimumWidth(240)
self.d2c_depth_combo.setMaxVisibleItems(12)
self.d2c_depth_combo.setMinimumWidth(_sz(280))
self.d2c_depth_combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self.d2c_depth_combo.setMaxVisibleItems(15)
self.d2c_depth_combo.setStyleSheet(COMBO_CSS)
combo_row.addWidget(self.d2c_depth_combo)
combo_row.addSpacing(12)
fetch_btn = QPushButton("从设备获取参数")
fetch_btn.setFixedHeight(28)
fetch_btn.setFixedHeight(_sz(28))
fetch_btn.clicked.connect(self._fetch_params_from_device)
combo_row.addWidget(fetch_btn)
combo_row.addStretch()
@@ -969,20 +1107,20 @@ class MainWindow(QMainWindow):
sep = QLabel("── 或直接指定 JSON 文件 ──")
sep.setAlignment(Qt.AlignCenter)
sep.setStyleSheet("color:#666; font-size:11px;")
sep.setStyleSheet(f"color:#666; font-size:{_fs(11)}px;")
pg.addWidget(sep)
json_row = QHBoxLayout()
self.params_path_edit = QLineEdit()
self.params_path_edit.setPlaceholderText("留空则使用上方自动查找结果…")
json_row.addWidget(self.params_path_edit)
br2 = QPushButton("浏览…"); br2.setFixedWidth(70)
br2 = QPushButton("浏览…"); br2.setFixedWidth(_sz(70))
br2.clicked.connect(lambda: self._browse_json(self.params_path_edit))
json_row.addWidget(br2)
pg.addLayout(json_row)
self.params_selected_lbl = QLabel("(尚未选定参数文件)")
self.params_selected_lbl.setStyleSheet("color:#888; font-size:12px;")
self.params_selected_lbl.setStyleSheet(f"color:#888; font-size:{_fs(12)}px;")
pg.addWidget(self.params_selected_lbl)
layout.addWidget(grp2)
@@ -994,7 +1132,7 @@ class MainWindow(QMainWindow):
self.d2c_dir_edit = QLineEdit()
self.d2c_dir_edit.setPlaceholderText("选择包含 PNG 深度图的文件夹…")
g3.addWidget(self.d2c_dir_edit)
br1 = QPushButton("浏览…"); br1.setFixedWidth(70)
br1 = QPushButton("浏览…"); br1.setFixedWidth(_sz(70))
br1.clicked.connect(lambda: self._browse_dir(self.d2c_dir_edit))
g3.addWidget(br1)
layout.addWidget(grp3)
@@ -1002,14 +1140,14 @@ class MainWindow(QMainWindow):
# ── 第 4 步:开始转换 ──
step4_row = QHBoxLayout()
step4_lbl = QLabel("第 4 步 · 开始转换")
step4_lbl.setStyleSheet("color:#8ec8ff; font-weight:bold; font-size:12px;")
step4_lbl.setStyleSheet(f"color:#8ec8ff; font-weight:bold; font-size:{_fs(12)}px;")
step4_row.addWidget(step4_lbl)
self.splat_check = QCheckBox("Splat 3×3 填充(减少 D2C 空洞,默认开启)")
self.splat_check.setChecked(True)
step4_row.addWidget(self.splat_check)
step4_row.addStretch()
self.run_d2c_btn = QPushButton("▶ 开始批量 D2C 转换")
self.run_d2c_btn.setFixedHeight(36)
self.run_d2c_btn.setFixedHeight(_sz(36))
self.run_d2c_btn.setStyleSheet(
"QPushButton{ background:#2a7a2a; color:white; border-radius:6px; border:none; }"
"QPushButton:hover{ background:#3a9a3a; }"
@@ -1024,7 +1162,7 @@ class MainWindow(QMainWindow):
self.d2c_log = QTextEdit()
self.d2c_log.setReadOnly(True)
self.d2c_log.setStyleSheet(
"font-family: Consolas,'Courier New',monospace; font-size:12px;")
f"font-family: Consolas,'Courier New',monospace; font-size:{_fs(12)}px;")
layout.addWidget(self.d2c_log, stretch=1)
return w
@@ -1404,6 +1542,14 @@ def main():
app = QApplication(sys.argv)
app.setStyle("Fusion")
global _UI_SCALE
_cfg = AppSettings()
_UI_SCALE = _cfg.get_scale_factor()
if _UI_SCALE != 1.0:
base = app.font()
base.setPointSizeF(base.pointSizeF() * _UI_SCALE)
app.setFont(base)
from PyQt5.QtGui import QPalette, QColor
pal = QPalette()
pal.setColor(QPalette.Window, QColor(45, 45, 45))