feat: 产品更名 DLPilot(3D 光固化光机调试台) + 按标记调整界面
This commit is contained in:
+56
-13
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
HDMI-Tool 主界面 — CustomTkinter 双栏仪表盘
|
||||
DLPilot 主界面 — CustomTkinter 双栏仪表盘
|
||||
================================================================
|
||||
布局(HDMI-Tool/UI参考布局.svg):
|
||||
布局(DLPilot/UI参考布局.svg):
|
||||
左列 显示器选择 | 支持的模式 | EDID 信息 | 测试图案/纯色/自定义 | EDID 原始数据
|
||||
右列 串口选择与参数 | 串口数据(收发日志) | 预设与自定义命令 | 光强/定时快捷滑条
|
||||
默认深色外观, 顶栏可切换 Dark/Light/System。
|
||||
@@ -12,6 +12,7 @@ HDMI-Tool 主界面 — CustomTkinter 双栏仪表盘
|
||||
投屏窗口在 patterns, 串口协议在 dlpc_rs485, 运行配置在 config。
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import ctypes
|
||||
import os
|
||||
import queue
|
||||
@@ -63,7 +64,11 @@ def run_gui(auto_close=0):
|
||||
class App(ctk.CTk):
|
||||
def __init__(self, auto_close=0):
|
||||
super().__init__()
|
||||
self.title('HDMI-Tool — EDID 查看器 + 测试图案 + 光机控制 (LT8619C 转接板调试)')
|
||||
self.title('DLPilot — 3D 光固化光机调试台 (EDID · 测试图案 · RS485 光机控制)')
|
||||
try: # 任务栏独立分组
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('hulk.DLPilot')
|
||||
except Exception:
|
||||
pass
|
||||
self.geometry('1440x920')
|
||||
self.minsize(1180, 800)
|
||||
ic = get_icon_path()
|
||||
@@ -97,8 +102,10 @@ class App(ctk.CTk):
|
||||
def _set_appearance(self, mode):
|
||||
ctk.set_appearance_mode(mode)
|
||||
|
||||
def _card(self, parent, title, button=None, command=None, right_label=None):
|
||||
"""卡片容器: 圆角边框 + 标题行(可带右侧小按钮/右对齐标签), 返回 (卡片, 内容 frame)
|
||||
def _card(self, parent, title, button=None, command=None, right_label=None,
|
||||
right_widget=None):
|
||||
"""卡片容器: 圆角边框 + 标题行(可带右侧小按钮/右对齐标签/自定义控件),
|
||||
返回 (卡片, 内容 frame)
|
||||
|
||||
卡片自身用 grid 参与列布局; 卡片内部(head/content/行控件)一律用 pack。
|
||||
"""
|
||||
@@ -107,6 +114,8 @@ class App(ctk.CTk):
|
||||
head = ctk.CTkFrame(card, fg_color='transparent')
|
||||
head.pack(fill='x', padx=12, pady=(8, 0))
|
||||
ctk.CTkLabel(head, text=title, font=BOLD, anchor='w').pack(side='left')
|
||||
if right_widget is not None: # 自定义控件(如 电流读数+查电流)
|
||||
right_widget.pack(side='right')
|
||||
if right_label is not None:
|
||||
ctk.CTkLabel(head, textvariable=right_label,
|
||||
text_color='gray60').pack(side='right')
|
||||
@@ -120,7 +129,7 @@ class App(ctk.CTk):
|
||||
def _build(self):
|
||||
top = ctk.CTkFrame(self, fg_color='transparent')
|
||||
top.pack(fill='x', padx=12, pady=(10, 2))
|
||||
ctk.CTkLabel(top, text='HDMI-Tool', font=TITLE_FONT).pack(side='left')
|
||||
ctk.CTkLabel(top, text='DLPilot', font=TITLE_FONT).pack(side='left')
|
||||
ctk.CTkLabel(top, text='EDID · 测试图案 · 光机控制',
|
||||
text_color='gray60').pack(side='left', padx=10, pady=(6, 0))
|
||||
seg = ctk.CTkSegmentedButton(top, values=['Dark', 'Light', 'System'],
|
||||
@@ -149,8 +158,8 @@ class App(ctk.CTk):
|
||||
).pack(expand=True)
|
||||
|
||||
def _build_left(self, col):
|
||||
col.grid_rowconfigure(1, weight=2) # 支持的模式(与 EDID 信息等高)
|
||||
col.grid_rowconfigure(2, weight=2) # EDID 信息
|
||||
col.grid_rowconfigure(1, weight=2) # 支持的模式
|
||||
col.grid_rowconfigure(2, weight=1) # EDID 信息(压缩高度)
|
||||
col.grid_rowconfigure(4, weight=0) # EDID 原始数据(固定高, 压缩)
|
||||
col.grid_columnconfigure(0, weight=1)
|
||||
|
||||
@@ -218,7 +227,7 @@ class App(ctk.CTk):
|
||||
row3 = ctk.CTkFrame(c, fg_color='transparent')
|
||||
row3.pack(fill='x', pady=(6, 0))
|
||||
self.slot_buttons = []
|
||||
for i in range(config.SLOT_COUNT):
|
||||
for i in range(3): # 自定义栏位(沿用原 3 槽配置)
|
||||
cell = ctk.CTkFrame(row3, fg_color='transparent')
|
||||
cell.pack(side='left', padx=(0, 10))
|
||||
btn = ctk.CTkButton(cell, text=self.slots[i]['name'], width=96,
|
||||
@@ -231,6 +240,22 @@ class App(ctk.CTk):
|
||||
self.slot_buttons.append(btn)
|
||||
ctk.CTkLabel(row3, text='点击投图, 齿轮按钮配置图片',
|
||||
text_color='gray50').pack(side='left', padx=4)
|
||||
# -- 光校图案(预留 5 槽): 3D 光固化打印测试/校准用图 --
|
||||
row4 = ctk.CTkFrame(c, fg_color='transparent')
|
||||
row4.pack(fill='x', pady=(6, 0))
|
||||
for i in range(3, config.SLOT_COUNT):
|
||||
cell = ctk.CTkFrame(row4, fg_color='transparent')
|
||||
cell.pack(side='left', padx=(0, 10))
|
||||
btn = ctk.CTkButton(cell, text=self.slots[i]['name'], width=96,
|
||||
command=lambda i=i: self._show_custom(i))
|
||||
btn.pack(side='left')
|
||||
ctk.CTkButton(cell, text='\uE713', width=30, height=24,
|
||||
font=('Segoe MDL2 Assets', 13),
|
||||
command=lambda i=i: self._configure_slot(i)
|
||||
).pack(side='left', padx=(4, 0))
|
||||
self.slot_buttons.append(btn)
|
||||
ctk.CTkLabel(row4, text='校准图案(预留 5 槽, 齿轮改图)',
|
||||
text_color='gray50').pack(side='left', padx=4)
|
||||
|
||||
# -- EDID 原始数据 --
|
||||
card, c = self._card(col, 'EDID 原始数据',
|
||||
@@ -293,12 +318,20 @@ class App(ctk.CTk):
|
||||
'累加和校验; 全部命令见 README"光机控制"')
|
||||
|
||||
# -- 光机命令 --
|
||||
card, c = self._card(col,'光机命令 — 预设 / 自定义 / TEC')
|
||||
# 标题行右侧: 电流实时读数 + 查电流按钮(okR..G..B.. 应答解析)
|
||||
cur_frame = ctk.CTkFrame(self, fg_color='transparent')
|
||||
self.var_current = tk.StringVar(value='电流: —')
|
||||
ctk.CTkLabel(cur_frame, textvariable=self.var_current, width=168,
|
||||
anchor='e', text_color='#3B8ED0').pack(side='left')
|
||||
ctk.CTkButton(cur_frame, text='查电流', width=64, height=24,
|
||||
command=self._query_current).pack(side='left', padx=(6, 0))
|
||||
card, c = self._card(col,'光机命令 — 预设 / 自定义 / TEC',
|
||||
right_widget=cur_frame)
|
||||
card.grid(row=2, column=0, sticky='ew', pady=(10, 0))
|
||||
row1 = ctk.CTkFrame(c, fg_color='transparent')
|
||||
row1.pack(fill='x')
|
||||
for name, cmd in (('开光机', 'M730S0'), ('关光机', 'M730S1'),
|
||||
('查电流', 'M732'), ('查版本', 'M999'), ('软件重启', 'M888'),
|
||||
('查版本', 'M999'), ('软件重启', 'M888'),
|
||||
('开TEC', 'M7S0'), ('关TEC', 'M7S1'), ('TEC33.0°C', 'M8S330')):
|
||||
ctk.CTkButton(row1, text=name, width=64,
|
||||
command=lambda cc=cmd: self._dlpc_send(cc)
|
||||
@@ -365,7 +398,7 @@ class App(ctk.CTk):
|
||||
textvariable=self.var_light)
|
||||
self.ent_light.pack(side='left')
|
||||
self.ent_light.bind('<Return>', self._light_entry_apply)
|
||||
ctk.CTkButton(rowa, text='下发 M731S', width=110,
|
||||
ctk.CTkButton(rowa, text='下发电流值', width=110,
|
||||
command=self._dlpc_send_light).pack(side='left', padx=(6, 0))
|
||||
rowb = ctk.CTkFrame(c, fg_color='transparent')
|
||||
rowb.pack(fill='x', pady=(8, 0))
|
||||
@@ -395,7 +428,7 @@ class App(ctk.CTk):
|
||||
textvariable=self.var_tec)
|
||||
self.ent_tec.pack(side='left')
|
||||
self.ent_tec.bind('<Return>', self._tec_entry_apply)
|
||||
ctk.CTkButton(rowc, text='下发 M8S', width=110,
|
||||
ctk.CTkButton(rowc, text='下发温度值', width=110,
|
||||
command=self._dlpc_send_tec).pack(side='left', padx=(6, 0))
|
||||
|
||||
# ============================================================
|
||||
@@ -634,8 +667,16 @@ class App(ctk.CTk):
|
||||
except ValueError:
|
||||
text = f'{which}: {m.group(1)}'
|
||||
self._dlpc_log_q.put(('temp', text))
|
||||
# 电流查询应答: okR<n>G<n>B<n> -> 标题行实时读数
|
||||
m = re.search(r'← okR(\d+)G(\d+)B(\d+)', line)
|
||||
if m:
|
||||
self._dlpc_log_q.put(('current', f'电流 R:{m.group(1)} '
|
||||
f'G:{m.group(2)} B:{m.group(3)}'))
|
||||
self._dlpc_log_q.put(('event', line))
|
||||
|
||||
def _query_current(self):
|
||||
self._dlpc_send('M732')
|
||||
|
||||
def _query_dmd(self):
|
||||
self._tl_order.append('DMD')
|
||||
self._dlpc_send('M201')
|
||||
@@ -666,6 +707,8 @@ class App(ctk.CTk):
|
||||
self.var_dmd.set(text)
|
||||
else:
|
||||
self.var_lamp.set(text)
|
||||
elif kind == 'current':
|
||||
self.var_current.set(text)
|
||||
self._dlpc_log(text)
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
把 hdmi_gui.py 打包成单文件 exe(工具名: HDMI-Tool)
|
||||
把 hdmi_gui.py 打包成单文件 exe(工具名: DLPilot)
|
||||
================================
|
||||
使用 PyInstaller --onefile --noconsole: Python 解释器 + 标准库 + tkinter
|
||||
全部打进一个 exe, 目标机器无需安装任何依赖。
|
||||
@@ -10,7 +10,7 @@
|
||||
python build_exe.py 自动安装 PyInstaller(如缺)并打包
|
||||
python build_exe.py --debug 额外打一个带控制台的变体(--dump 可用)
|
||||
|
||||
产物: release/HDMI-Tool.exe(及 --debug 时的 HDMI-Tool-console.exe)
|
||||
产物: release/DLPilot.exe(及 --debug 时的 DLPilot-console.exe)
|
||||
首次运行 onefile exe 会先解压到临时目录, 启动比脚本慢一两秒, 属正常现象。
|
||||
"""
|
||||
|
||||
@@ -84,10 +84,10 @@ def main():
|
||||
build_console = '--debug' in sys.argv
|
||||
ensure_pyinstaller()
|
||||
# 常规版: 双击即用, 无黑框(GUI/图案/自定义栏位全部可用, --dump 除外)
|
||||
build('HDMI-Tool', ['--noconsole'])
|
||||
build('DLPilot', ['--noconsole'])
|
||||
if build_console:
|
||||
# 调试版: 带控制台, --dump / 异常栈可见
|
||||
build('HDMI-Tool-console', [])
|
||||
build('DLPilot-console', [])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -39,7 +39,7 @@ 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__))
|
||||
SLOT_COUNT = 3
|
||||
SLOT_COUNT = 8
|
||||
SLOT_CONFIG = os.path.join(BASE_DIR, 'pattern_slots.json')
|
||||
IMG_EXTS = ['.png', '.gif'] + (['.jpg', '.jpeg', '.bmp', '.webp'] if HAS_PIL else [])
|
||||
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
组装 HDMI-Tool 发布包: exe + 附属资产 → 一个文件夹 + 一个 zip(解压即用)
|
||||
组装 DLPilot 发布包: exe + 附属资产 → 一个文件夹 + 一个 zip(解压即用)
|
||||
================================================================
|
||||
先把 assets/ 的图案复制进 release/(与 exe 平铺), 使 release/ 本身就是
|
||||
可直接使用的完整文件夹; 再原样压成 zip —— 两者内容始终一致。
|
||||
|
||||
zip 解压后根目录为 HDMI-Tool/, 与 exe 运行目录结构一致:
|
||||
HDMI-Tool.exe 主程序(先运行 build_exe.py 打包)
|
||||
zip 解压后根目录为 DLPilot/, 与 exe 运行目录结构一致:
|
||||
DLPilot.exe 主程序(先运行 build_exe.py 打包)
|
||||
pattern_slots.json 预置自定义栏位(指向前 3 张 bw 图案)
|
||||
bw_*.png 640x360 黑白测试图 ×5(自定义栏位用)
|
||||
solid_*.png 800x600 纯色测试图 ×10(RGB 位交换测试用)
|
||||
README.md 使用说明
|
||||
|
||||
用法:
|
||||
python package_release.py 版本号自动取最近 git tag(HDMI-Tool-v*)
|
||||
python package_release.py 版本号自动取最近 git tag(DLPilot-v*)
|
||||
python package_release.py 0.1.0 手动指定版本号
|
||||
产物: release/HDMI-Tool-v<版本>-win64.zip(zip 不入库, 上传到 Gitea Release)
|
||||
产物: release/DLPilot-v<版本>-win64.zip(zip 不入库, 上传到 Gitea Release)
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -35,23 +35,23 @@ PATTERN_GLOBS = ('bw_*.png', 'solid_*.png')
|
||||
|
||||
|
||||
def detect_version():
|
||||
"""从最近的 HDMI-Tool-v* tag 推断版本号, 无 tag 则报错提示手动指定"""
|
||||
"""从最近的 DLPilot-v* tag 推断版本号, 无 tag 则报错提示手动指定"""
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
['git', 'describe', '--tags', '--abbrev=0', '--match', 'HDMI-Tool-v*'],
|
||||
['git', 'describe', '--tags', '--abbrev=0', '--match', 'DLPilot-v*'],
|
||||
cwd=BASE, stderr=subprocess.DEVNULL)
|
||||
return out.decode().strip().removeprefix('HDMI-Tool-v')
|
||||
return out.decode().strip().removeprefix('DLPilot-v')
|
||||
except Exception:
|
||||
sys.exit('未能从 git tag 推断版本号, 请手动指定: python package_release.py <版本号>')
|
||||
|
||||
|
||||
def main():
|
||||
version = (sys.argv[1] if len(sys.argv) > 1 else detect_version()).lstrip('v')
|
||||
exe = os.path.join(RELEASE, 'HDMI-Tool.exe')
|
||||
exe = os.path.join(RELEASE, 'DLPilot.exe')
|
||||
slots = os.path.join(RELEASE, 'pattern_slots.json')
|
||||
readme = os.path.join(BASE, 'README.md')
|
||||
if not os.path.exists(exe):
|
||||
sys.exit('缺少 release/HDMI-Tool.exe, 先运行: python build_exe.py')
|
||||
sys.exit('缺少 release/DLPilot.exe, 先运行: python build_exe.py')
|
||||
for must in (slots, readme):
|
||||
if not os.path.exists(must):
|
||||
sys.exit(f'缺少 {must}')
|
||||
@@ -70,11 +70,11 @@ def main():
|
||||
shutil.copy2(os.path.join(ASSETS, p), os.path.join(RELEASE, p))
|
||||
shutil.copy2(readme, os.path.join(RELEASE, 'README.md'))
|
||||
|
||||
zip_name = f'HDMI-Tool-v{version}-win64.zip'
|
||||
zip_name = f'DLPilot-v{version}-win64.zip'
|
||||
zip_path = os.path.join(RELEASE, zip_name)
|
||||
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as z:
|
||||
for p in ['HDMI-Tool.exe', 'pattern_slots.json', 'README.md'] + patterns:
|
||||
z.write(os.path.join(RELEASE, p), f'HDMI-Tool/{p}')
|
||||
for p in ['DLPilot.exe', 'pattern_slots.json', 'README.md'] + patterns:
|
||||
z.write(os.path.join(RELEASE, p), f'DLPilot/{p}')
|
||||
print(f'发布文件夹: {RELEASE} (exe+配置+说明+{len(patterns)} 张图案, 平铺即用)')
|
||||
print(f'发布包: {zip_path} ({os.path.getsize(zip_path) / 1024 / 1024:.1f} MB)')
|
||||
print('下一步上传: python publish_gitea.py', zip_name)
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
创建/更新 Gitea Release 并上传发布 zip(纯标准库, 无需安装依赖)
|
||||
================================================================
|
||||
用法:
|
||||
python publish_gitea.py HDMI-Tool-v0.1.0-win64.zip
|
||||
python publish_gitea.py DLPilot-v0.1.0-win64.zip
|
||||
python publish_gitea.py <zip> --notes-file release/notes-v0.1.0.md
|
||||
python publish_gitea.py <zip> --tag HDMI-Tool-v0.1.0 --replace
|
||||
python publish_gitea.py <zip> --tag DLPilot-v0.1.0 --replace
|
||||
|
||||
行为:
|
||||
- tag 与版本默认取 zip 文件名(HDMI-Tool-vX.Y.Z-win64.zip → HDMI-Tool-vX.Y.Z)
|
||||
- tag 与版本默认取 zip 文件名(DLPilot-vX.Y.Z-win64.zip → DLPilot-vX.Y.Z)
|
||||
- release 不存在则创建(tag 不存在时 Gitea 在默认分支头自动建 tag);
|
||||
已存在则复用, 仅上传/更新资产
|
||||
- release 上已有同名资产时: 默认跳过, --replace 删旧再传
|
||||
@@ -27,16 +27,16 @@ import urllib.error
|
||||
import urllib.request
|
||||
import uuid
|
||||
|
||||
DEFAULT_NOTES = """## HDMI-Tool {version}
|
||||
DEFAULT_NOTES = """## DLPilot {version}
|
||||
|
||||
HDMI 源端调试工具(LT8619C → DLPC3421 转接板配套): EDID 查看/逐字节解析、
|
||||
分辨率切换、纯色与测试图案全屏投屏。
|
||||
|
||||
### 安装
|
||||
下载 `HDMI-Tool-v{version}-win64.zip`, 解压后双击 `HDMI-Tool.exe`(Windows 10/11 x64, 免安装)。
|
||||
下载 `DLPilot-v{version}-win64.zip`, 解压后双击 `DLPilot.exe`(Windows 10/11 x64, 免安装)。
|
||||
|
||||
### 包内容
|
||||
- `HDMI-Tool.exe` — 主程序
|
||||
- `DLPilot.exe` — 主程序
|
||||
- `pattern_slots.json` — 预置自定义栏位(指向 bw_*.png)
|
||||
- `bw_*.png` ×5 / `solid_*.png` ×10 — 测试图案
|
||||
- `README.md` — 完整使用说明
|
||||
@@ -119,7 +119,7 @@ def main():
|
||||
sys.exit('缺少 API token: 传 --token 或设环境变量 GITEA_TOKEN')
|
||||
|
||||
tag = args.tag or re.sub(r'-win64\.zip$', '', os.path.basename(args.zipfile_path))
|
||||
version = tag.removeprefix('HDMI-Tool-v')
|
||||
version = tag.removeprefix('DLPilot-v')
|
||||
notes = open(args.notes_file, encoding='utf-8').read() if args.notes_file \
|
||||
else DEFAULT_NOTES.format(version=version)
|
||||
|
||||
@@ -133,7 +133,7 @@ def main():
|
||||
else:
|
||||
status, rel = api.request('POST', '/releases', json.dumps({
|
||||
'tag_name': tag,
|
||||
'name': f'HDMI-Tool v{version}',
|
||||
'name': f'DLPilot v{version}',
|
||||
'body': notes,
|
||||
}).encode(), {'Content-Type': 'application/json'})
|
||||
if status != 201:
|
||||
|
||||
Reference in New Issue
Block a user