diff --git a/1.txt b/1.txt new file mode 100644 index 0000000..a05df33 --- /dev/null +++ b/1.txt @@ -0,0 +1 @@ +hello,world diff --git a/RF.py b/RF.py new file mode 100644 index 0000000..1be4df2 --- /dev/null +++ b/RF.py @@ -0,0 +1,457 @@ +import tkinter as tk +from tkinter import ttk, scrolledtext, messagebox +import math + +class RFToolbox: + def __init__(self, root): + self.root = root + self.root.title("RF Toolbox V1.0") + self.root.geometry("800x600") + self.root.configure(bg="#f0f0f0") + + # 存储计算历史 + self.calc_history = [] + + # 创建主布局(左侧导航+右侧内容) + self.create_navigation() + self.create_content_frame() + + # 默认显示首页 + self.show_home() + + def create_navigation(self): + """创建左侧导航栏""" + nav_frame = tk.Frame(self.root, width=150, bg="#333", height=600, relief="sunken", bd=2) + nav_frame.pack(side=tk.LEFT, fill=tk.Y) + nav_frame.pack_propagate(False) + + # 导航按钮 + buttons = [ + ("首页", self.show_home), + ("频率-波长计算", self.show_freq_wavelength), + ("功率单位换算", self.show_power_converter), + ("传输线参数计算", self.show_transmission_line), + ("射频常识库", self.show_knowledge_base), + ("计算历史", self.show_history) + ] + + for text, command in buttons: + btn = tk.Button( + nav_frame, text=text, command=command, + bg="#555", fg="white", width=15, height=2, + relief="flat", font=("SimHei", 10) + ) + btn.pack(pady=5, padx=5) + btn.bind("", lambda e, b=btn: b.config(bg="#777")) + btn.bind("", lambda e, b=btn: b.config(bg="#555")) + + def create_content_frame(self): + """创建右侧内容显示区""" + self.content_frame = tk.Frame(self.root, bg="#f0f0f0", width=650, height=600) + self.content_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) + + def clear_content(self): + """清空内容区""" + for widget in self.content_frame.winfo_children(): + widget.destroy() + + # ------------------------------ + # 首页 + # ------------------------------ + def show_home(self): + self.clear_content() + title = tk.Label( + self.content_frame, text="RF Toolbox 射频工具箱", + font=("SimHei", 20, "bold"), bg="#f0f0f0", pady=20 + ) + title.pack() + + desc = tk.Label( + self.content_frame, + text="一款专注于射频领域基础计算与常识查询的工具\n" + "支持频率-波长转换、功率单位换算、传输线参数计算等功能", + font=("SimHei", 12), bg="#f0f0f0", justify=tk.CENTER + ) + desc.pack(pady=10) + + # 快捷功能入口 + tk.Label(self.content_frame, text="常用工具", font=("SimHei", 14, "bold"), bg="#f0f0f0").pack(pady=10) + tools_frame = tk.Frame(self.content_frame, bg="#f0f0f0") + tools_frame.pack(pady=10) + + tools = [ + ("频率-波长计算", self.show_freq_wavelength), + ("功率单位换算", self.show_power_converter), + ("传输线参数计算", self.show_transmission_line) + ] + + for i, (text, cmd) in enumerate(tools): + btn = tk.Button( + tools_frame, text=text, command=cmd, + width=20, height=3, font=("SimHei", 11), + bg="#4CAF50", fg="white", relief="raised" + ) + btn.grid(row=i//2, column=i%2, padx=10, pady=10) + + # ------------------------------ + # 1. 频率-波长计算器 + # ------------------------------ + def show_freq_wavelength(self): + self.clear_content() + tk.Label( + self.content_frame, text="频率-波长计算器", + font=("SimHei", 16, "bold"), bg="#f0f0f0" + ).pack(pady=10) + + frame = tk.Frame(self.content_frame, bg="#f0f0f0") + frame.pack(padx=20, fill=tk.X) + + # 输入频率 + tk.Label(frame, text="输入频率:", font=("SimHei", 12), bg="#f0f0f0").grid(row=0, column=0, pady=10) + self.freq_value = tk.StringVar() + ttk.Entry(frame, textvariable=self.freq_value, width=15, font=("SimHei", 12)).grid(row=0, column=1) + + self.freq_unit = tk.StringVar(value="GHz") + ttk.Combobox( + frame, textvariable=self.freq_unit, + values=["Hz", "KHz", "MHz", "GHz"], width=8 + ).grid(row=0, column=2, padx=5) + + # 介质介电常数 + tk.Label(frame, text="介质介电常数 (ε):", font=("SimHei", 12), bg="#f0f0f0").grid(row=1, column=0, pady=10) + self.epsilon = tk.StringVar(value="1") # 空气默认1 + ttk.Entry(frame, textvariable=self.epsilon, width=15, font=("SimHei", 12)).grid(row=1, column=1) + + # 计算按钮 + calc_btn = tk.Button( + frame, text="计算波长", command=self.calc_wavelength, + bg="#2196F3", fg="white", font=("SimHei", 12) + ) + calc_btn.grid(row=2, column=0, columnspan=3, pady=10) + + # 结果显示 + self.wavelength_result = tk.StringVar() + tk.Label( + self.content_frame, textvariable=self.wavelength_result, + font=("SimHei", 12), bg="#f0f0f0", fg="red" + ).pack(pady=20) + + # 公式说明 + tk.Label( + self.content_frame, text="公式:λ = c / (f × √ε) (c=3×10^8 m/s)", + font=("SimHei", 10), bg="#f0f0f0", fg="#666" + ).pack(anchor=tk.W, padx=20) + + def calc_wavelength(self): + try: + # 解析输入 + freq = float(self.freq_value.get()) + unit = self.freq_unit.get() + epsilon = float(self.epsilon.get()) + + # 频率单位转换为Hz + unit_factor = { + "Hz": 1, + "KHz": 1e3, + "MHz": 1e6, + "GHz": 1e9 + }[unit] + freq_hz = freq * unit_factor + + # 计算波长(米) + c = 3e8 # 光速 + wavelength_m = c / (freq_hz * math.sqrt(epsilon)) + + # 转换为合适单位 + if wavelength_m >= 1: + result = f"波长 = {wavelength_m:.3f} 米 (m)" + elif wavelength_m >= 0.01: + result = f"波长 = {wavelength_m*100:.3f} 厘米 (cm)" + else: + result = f"波长 = {wavelength_m*1000:.3f} 毫米 (mm)" + + self.wavelength_result.set(result) + self.calc_history.append(f"频率-波长:{freq}{unit} → {result}") + + except ValueError: + messagebox.showerror("输入错误", "请输入有效的数字!") + + # ------------------------------ + # 2. 功率单位换算器 + # ------------------------------ + def show_power_converter(self): + self.clear_content() + tk.Label( + self.content_frame, text="功率单位换算器", + font=("SimHei", 16, "bold"), bg="#f0f0f0" + ).pack(pady=10) + + frame = tk.Frame(self.content_frame, bg="#f0f0f0") + frame.pack(padx=20, fill=tk.X) + + # 输入功率 + tk.Label(frame, text="输入功率:", font=("SimHei", 12), bg="#f0f0f0").grid(row=0, column=0, pady=10) + self.power_value = tk.StringVar() + ttk.Entry(frame, textvariable=self.power_value, width=15, font=("SimHei", 12)).grid(row=0, column=1) + + self.power_unit = tk.StringVar(value="dBm") + ttk.Combobox( + frame, textvariable=self.power_unit, + values=["dBm", "W", "dBW"], width=8 + ).grid(row=0, column=2, padx=5) + + # 转换按钮 + calc_btn = tk.Button( + frame, text="转换", command=self.convert_power, + bg="#2196F3", fg="white", font=("SimHei", 12) + ).grid(row=1, column=0, columnspan=3, pady=10) + + # 结果显示 + self.power_result = tk.Text(self.content_frame, height=5, width=50, font=("SimHei", 12)) + self.power_result.pack(pady=20) + + # 公式说明 + tk.Label( + self.content_frame, + text="公式:\n" + "dBm = 10×log10(P(mW))\n" + "dBW = 10×log10(P(W))\n" + "1 W = 1000 mW = 30 dBm = 0 dBW", + font=("SimHei", 10), bg="#f0f0f0", fg="#666", justify=tk.LEFT + ).pack(anchor=tk.W, padx=20) + + def convert_power(self): + try: + power = float(self.power_value.get()) + unit = self.power_unit.get() + result = [] + + if unit == "dBm": + # dBm → mW → W → dBW + mw = 10 **(power / 10) + w = mw / 1000 + dbw = power - 30 + result = [ + f"{power} dBm = {mw:.3f} mW", + f"{power} dBm = {w:.6f} W", + f"{power} dBm = {dbw:.3f} dBW" + ] + + elif unit == "W": + # W → mW → dBm → dBW + mw = power * 1000 + dbm = 10 * math.log10(mw) if mw > 0 else -float('inf') + dbw = 10 * math.log10(power) if power > 0 else -float('inf') + result = [ + f"{power} W = {mw:.3f} mW", + f"{power} W = {dbm:.3f} dBm", + f"{power} W = {dbw:.3f} dBW" + ] + + elif unit == "dBW": + # dBW → W → mW → dBm + w = 10** (power / 10) + mw = w * 1000 + dbm = power + 30 + result = [ + f"{power} dBW = {w:.6f} W", + f"{power} dBW = {mw:.3f} mW", + f"{power} dBW = {dbm:.3f} dBm" + ] + + self.power_result.delete(1.0, tk.END) + self.power_result.insert(tk.END, "\n".join(result)) + self.calc_history.append(f"功率转换:{power}{unit} → {result[0]}") + + except ValueError: + messagebox.showerror("输入错误", "请输入有效的数字!") + except Exception as e: + messagebox.showerror("错误", f"计算失败:{str(e)}") + + # ------------------------------ + # 3. 传输线参数计算(VSWR/反射系数/回波损耗) + # ------------------------------ + def show_transmission_line(self): + self.clear_content() + tk.Label( + self.content_frame, text="传输线参数计算", + font=("SimHei", 16, "bold"), bg="#f0f0f0" + ).pack(pady=10) + + frame = tk.Frame(self.content_frame, bg="#f0f0f0") + frame.pack(padx=20, fill=tk.X) + + # 输入参数类型选择 + tk.Label(frame, text="输入参数类型:", font=("SimHei", 12), bg="#f0f0f0").grid(row=0, column=0, pady=10) + self.tline_param_type = tk.StringVar(value="VSWR") + ttk.Combobox( + frame, textvariable=self.tline_param_type, + values=["VSWR", "反射系数(Γ)", "回波损耗(dB)"], width=15 + ).grid(row=0, column=1, padx=5) + + # 输入值 + tk.Label(frame, text="输入值:", font=("SimHei", 12), bg="#f0f0f0").grid(row=1, column=0, pady=10) + self.tline_value = tk.StringVar() + ttk.Entry(frame, textvariable=self.tline_value, width=15, font=("SimHei", 12)).grid(row=1, column=1) + + # 计算按钮 + calc_btn = tk.Button( + frame, text="计算其他参数", command=self.calc_transmission_line, + bg="#2196F3", fg="white", font=("SimHei", 12) + ).grid(row=2, column=0, columnspan=2, pady=10) + + # 结果显示 + self.tline_result = tk.Text(self.content_frame, height=5, width=50, font=("SimHei", 12)) + self.tline_result.pack(pady=20) + + # 公式说明 + tk.Label( + self.content_frame, + text="公式:\n" + "VSWR = (1 + |Γ|) / (1 - |Γ|)\n" + "回波损耗 RL(dB) = -20×log10(|Γ|)\n" + "(Γ为反射系数,范围0~1)", + font=("SimHei", 10), bg="#f0f0f0", fg="#666", justify=tk.LEFT + ).pack(anchor=tk.W, padx=20) + + def calc_transmission_line(self): + try: + value = float(self.tline_value.get()) + param_type = self.tline_param_type.get() + result = [] + + if param_type == "VSWR": + if value < 1: + raise ValueError("VSWR值必须≥1") + gamma = (value - 1) / (value + 1) # 反射系数 + rl = -20 * math.log10(gamma) # 回波损耗 + result = [ + f"VSWR = {value} → 反射系数 Γ = {gamma:.3f}", + f"VSWR = {value} → 回波损耗 RL = {rl:.3f} dB" + ] + + elif param_type == "反射系数(Γ)": + if not (0 <= value <= 1): + raise ValueError("反射系数范围应为0~1") + vswr = (1 + value) / (1 - value) + rl = -20 * math.log10(value) + result = [ + f"反射系数 Γ = {value} → VSWR = {vswr:.3f}", + f"反射系数 Γ = {value} → 回波损耗 RL = {rl:.3f} dB" + ] + + elif param_type == "回波损耗(dB)": + if value < 0: + raise ValueError("回波损耗值应为正数") + gamma = 10 **(-value / 20) + vswr = (1 + gamma) / (1 - gamma) + result = [ + f"回波损耗 RL = {value} dB → 反射系数 Γ = {gamma:.3f}", + f"回波损耗 RL = {value} dB → VSWR = {vswr:.3f}" + ] + + self.tline_result.delete(1.0, tk.END) + self.tline_result.insert(tk.END, "\n".join(result)) + self.calc_history.append(f"传输线参数:{value}{param_type} → {result[0]}") + + except ValueError as e: + messagebox.showerror("输入错误", str(e)) + except Exception as e: + messagebox.showerror("错误", f"计算失败:{str(e)}") + + # ------------------------------ + # 4. 射频常识库(简化版) + # ------------------------------ + def show_knowledge_base(self): + self.clear_content() + tk.Label( + self.content_frame, text="射频常识库", + font=("SimHei", 16, "bold"), bg="#f0f0f0" + ).pack(pady=10) + + # 创建标签页分类显示 + notebook = ttk.Notebook(self.content_frame) + notebook.pack(padx=20, pady=10, fill=tk.BOTH, expand=True) + + # 1. 核心概念 + concept_frame = tk.Frame(notebook, bg="#f0f0f0") + notebook.add(concept_frame, text="核心概念") + concept_text = scrolledtext.ScrolledText(concept_frame, wrap=tk.WORD, font=("SimHei", 10)) + concept_text.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) + concept_text.insert(tk.END, """ +1. 频率与波长 + - 频率(f):电磁波每秒振动的次数,单位Hz(赫兹),常用单位:KHz(1e3)、MHz(1e6)、GHz(1e9) + - 波长(λ):电磁波在一个周期内传播的距离,单位米(m) + - 关系:λ = c / f (真空中c=3×10^8 m/s) + +2. 功率单位 + - dBm:以1mW为参考的功率分贝,公式:dBm = 10×log10(P(mW)) + - dBW:以1W为参考的功率分贝,公式:dBW = 10×log10(P(W)) + - 换算:1W = 30 dBm = 0 dBW;0 dBm = 1 mW + +3. 传输线关键参数 + - 驻波比(VSWR):衡量阻抗匹配程度,理想值=1(完全匹配),值越大匹配越差 + - 反射系数(Γ):反射波与入射波的振幅比,范围0~1,0表示无反射 + - 回波损耗(RL):反射功率的衰减量,单位dB,值越大反射越小(匹配越好) + """) + concept_text.config(state=tk.DISABLED) + + # 2. 频段划分 + band_frame = tk.Frame(notebook, bg="#f0f0f0") + notebook.add(band_frame, text="频段划分") + band_text = scrolledtext.ScrolledText(band_frame, wrap=tk.WORD, font=("SimHei", 10)) + band_text.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) + band_text.insert(tk.END, """ +常用射频频段划分: +- HF(高频):3~30 MHz,用于短波通信 +- VHF(甚高频):30~300 MHz,用于FM广播、电视、对讲机 +- UHF(特高频):300 MHz~3 GHz,用于WiFi(2.4GHz)、蓝牙、电视 +- SHF(超高频):3~30 GHz,用于5G(Sub-6GHz)、卫星通信、雷达 +- EHF(极高频):30~300 GHz,用于毫米波雷达、6G试验频段 + +5G NR主要频段: +- FR1(Sub-6GHz):450 MHz~6 GHz(主流商用频段) +- FR2(毫米波):24.25~52.6 GHz(高速率场景) + """) + band_text.config(state=tk.DISABLED) + + # ------------------------------ + # 5. 计算历史 + # ------------------------------ + def show_history(self): + self.clear_content() + tk.Label( + self.content_frame, text="计算历史", + font=("SimHei", 16, "bold"), bg="#f0f0f0" + ).pack(pady=10) + + history_text = scrolledtext.ScrolledText(self.content_frame, wrap=tk.WORD, font=("SimHei", 11)) + history_text.pack(padx=20, pady=10, fill=tk.BOTH, expand=True) + + if not self.calc_history: + history_text.insert(tk.END, "暂无计算记录") + else: + for i, record in enumerate(reversed(self.calc_history[-10:]), 1): # 显示最近10条 + history_text.insert(tk.END, f"{i}. {record}\n\n") + + history_text.config(state=tk.DISABLED) + + # 清空历史按钮 + clear_btn = tk.Button( + self.content_frame, text="清空历史", command=lambda: self.clear_history(history_text), + bg="#f44336", fg="white", font=("SimHei", 10) + ) + clear_btn.pack(pady=10) + + def clear_history(self, text_widget): + self.calc_history = [] + text_widget.config(state=tk.NORMAL) + text_widget.delete(1.0, tk.END) + text_widget.insert(tk.END, "暂无计算记录") + text_widget.config(state=tk.DISABLED) + + +if __name__ == "__main__": + root = tk.Tk() + app = RFToolbox(root) + root.mainloop() diff --git a/RF_UPGRADE_PLAN.md b/RF_UPGRADE_PLAN.md new file mode 100644 index 0000000..5e7a293 --- /dev/null +++ b/RF_UPGRADE_PLAN.md @@ -0,0 +1,204 @@ +# RF Toolbox 升级计划 + +## 一、项目概述 +将现有的RF.py程序升级为一个功能全面的射频工程专业设计平台,满足用户要求的17项功能。 + +## 二、项目结构设计 +采用模块化设计,将不同功能划分为独立模块,提高代码可维护性和扩展性。 + +``` +RF_Toolbox_Professional/ +├── main.py # 主程序入口 +├── ui/ # 用户界面模块 +│ ├── main_window.py # 主窗口设计 +│ ├── themes.py # 主题管理 +│ ├── navigation.py # 导航系统 +│ └── widgets.py # 自定义控件 +├── tools/ # 工具模块 +│ ├── frequency_wavelength.py # 频率-波长计算器 +│ ├── power_converter.py # 功率单位换算 +│ ├── transmission_line.py # 传输线计算 +│ ├── antenna_calculator.py # 天线参数计算器 +│ ├── filter_designer.py # 滤波器设计工具 +│ └── link_budget.py # 射频链路预算分析 +├── data/ # 数据管理模块 +│ ├── project_manager.py # 项目管理 +│ ├── history_manager.py # 历史记录管理 +│ ├── database.py # SQLite数据库 +│ └── export.py # 数据导出 +├── knowledge/ # 知识库模块 +│ ├── knowledge_base.py # 结构化知识库 +│ ├── learning_tools.py # 交互式学习工具 +│ └── tutorials.py # 内置教程 +├── utils/ # 工具函数模块 +│ ├── calculations.py # 核心计算函数 +│ ├── unit_conversion.py # 单位转换 +│ ├── visualization.py # 可视化功能 +│ └── config.py # 配置管理 +├── plugins/ # 插件系统 +│ ├── base_plugin.py # 插件基类 +│ └── sample_plugin.py # 示例插件 +└── resources/ # 资源文件 + ├── icons/ # 矢量图标 + ├── themes/ # 主题文件 + └── templates/ # 报告模板 +``` + +## 三、实现步骤 + +### 阶段一:基础架构搭建(1-2周) +1. 创建新的项目结构 +2. 实现主窗口和基本导航系统 +3. 添加主题切换功能(深色/浅色/跟随系统) +4. 实现响应式布局 +5. 添加等宽字体支持 + +### 阶段二:现有工具增强(2-3周) +1. 频率-波长计算器 + - 添加批量计算功能(起始/终止/步进) + - 添加常用频段预设(5G NR/WiFi/卫星等) + - 添加介质材料库(FR4/Rogers等) + - 实现单位自动优化 + +2. 功率单位换算 + - 实现双向实时换算 + - 添加链路预算计算功能 + - 添加NF/噪声温度换算 + - 添加动态范围计算 + +3. 传输线计算 + - 添加L/T/π型阻抗匹配网络设计 + - 实现S参数相互换算 + - 添加史密斯圆图可视化 + - 添加传输线参数库 + - 实现导体/介质损耗计算 + +### 阶段三:新增专业模块(3-4周) +1. 天线参数计算器 + - 实现增益/波束宽度/有效孔径计算 + - 添加线性/平面阵列天线计算 + - 支持线极化/圆极化选择 + - 实现天线方向图3D可视化 + +2. 滤波器设计工具 + - 支持低通/高通/带通/带阻设计 + - 实现巴特沃斯/切比雪夫/椭圆类型滤波器 + - 计算LC元件值 + - 添加微带滤波器初步设计 + +3. 射频链路预算分析 + - 实现系统级联分析(增益/NF/三阶交调点) + - 添加灵敏度计算 + - 实现动态范围分析 + - 支持指标预算分配 + +### 阶段四:数据管理和输出(2-3周) +1. 项目管理 + - 实现项目创建和管理 + - 添加多格式导出(CSV/JSON/PDF) + - 自动生成计算报告 + - 提供常用场景模板 + +2. 高级历史管理 + - 实现按工具类型分类记录 + - 添加搜索和筛选功能 + - 支持计算结果对比 + - 实现重要结果收藏 + +### 阶段五:知识库系统升级(2-3周) +1. 结构化知识体系 + - 实现基础理论、器件知识、系统设计、标准规范四大分类 + - 添加详细的知识点内容 + +2. 交互式学习工具 + - 实现公式推导展示 + - 添加参数影响分析 + - 提供典型案例分析 + - 实现在线练习功能 + +### 阶段六:技术架构优化(1-2周) +1. 性能优化 + - 实现多线程计算引擎 + - 添加结果缓存机制 + - 实现大型模块懒加载 + - 优化内存管理 + +2. 可扩展架构 + - 实现第三方插件系统 + - 添加参数配置化管理 + - 提供高级用户API接口 + - 完善模块化设计 + +3. 数据持久化 + - 实现SQLite本地数据库 + - 添加输入实时自动保存 + - 实现定期数据备份 + - 提供可选云同步功能 + +### 阶段七:专业特性和用户体验优化(2-3周) +1. 工程实用功能 + - 实现单位智能转换 + - 添加计算精度误差分析 + - 考虑工程设计余量 + - 实现标准符合性检查 + +2. 可视化增强 + - 添加参数变化实时图表 + - 实现多参数扫描分析 + - 提供可选3D显示 + - 实现交互式图表查看和导出 + +3. 操作流程优化 + - 实现复杂计算分步向导 + - 添加上下文智能默认值 + - 实现输入实时验证和修正 + - 提供键盘快捷键支持 + +4. 帮助系统 + - 添加输入框上下文帮助 + - 实现控件工具提示 + - 提供内置视频和图文教程 + - 完善常见问题排查指南 + +### 阶段八:商业化和用户反馈(1-2周) +1. 版本规划 + - 实现免费版(基础功能+知识库) + - 开发专业版(付费高级功能) + - 提供企业版(定制服务) + +2. 用户反馈机制 + - 添加内置反馈工具 + - 实现匿名使用统计 + - 提供功能请求投票系统 + - 定期进行用户调研 + +## 四、技术选型 +- **GUI框架**:PyQt5(现代化界面、响应式设计、丰富的控件库) +- **可视化库**:Matplotlib(图表绘制)、PyQtGraph(实时数据可视化)、Mayavi(3D可视化) +- **数据库**:SQLite(本地数据存储) +- **导出功能**:Pandas(CSV/JSON导出)、ReportLab(PDF生成) +- **多线程**:Python threading模块 +- **插件系统**:动态加载模块(importlib) + +## 五、时间安排 +总预计开发时间:18-25周 + +## 六、风险评估 +1. **技术复杂度**:部分功能(如史密斯圆图、3D天线方向图)技术复杂度较高,需要充分的研究和测试 +2. **性能优化**:多线程计算和内存管理需要仔细设计,避免出现问题 +3. **用户体验**:需要充分考虑用户需求,进行多次迭代和测试 +4. **时间管理**:项目规模较大,需要严格的时间管理和进度控制 + +## 七、质量保证 +1. 每个模块完成后进行单元测试 +2. 定期进行集成测试 +3. 邀请射频工程师进行专业测试 +4. 收集用户反馈并及时改进 + +## 八、后续维护 +1. 定期更新知识库内容 +2. 添加新的功能模块 +3. 修复bug和优化性能 +4. 提供技术支持和培训 + +这个升级计划将使RF Toolbox从简单的计算器转变为一个功能全面的射频工程专业设计平台,满足从初学者到专业工程师的各种需求。 \ No newline at end of file diff --git a/data/history_manager.py b/data/history_manager.py new file mode 100644 index 0000000..f30c1c0 --- /dev/null +++ b/data/history_manager.py @@ -0,0 +1,416 @@ +import sqlite3 +import json +import csv +import os +from datetime import datetime + +class HistoryManager: + def __init__(self): + self.db_path = 'data/rf_toolbox.db' + self.init_database() + + def init_database(self): + # 初始化数据库 + os.makedirs(os.path.dirname(self.db_path), exist_ok=True) + + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # 创建历史记录表格 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tool_name TEXT NOT NULL, + input_data TEXT NOT NULL, + output_data TEXT NOT NULL, + calculation_type TEXT NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + is_favorite INTEGER DEFAULT 0 + ) + ''') + + # 创建项目表格 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS projects ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + data TEXT + ) + ''') + + conn.commit() + conn.close() + + def add_history(self, tool_name, input_data, output_data, calculation_type, is_favorite=False): + # 添加历史记录 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute(''' + INSERT INTO history (tool_name, input_data, output_data, calculation_type, is_favorite) + VALUES (?, ?, ?, ?, ?) + ''', (tool_name, input_data, output_data, calculation_type, int(is_favorite))) + + conn.commit() + conn.close() + + def get_history(self, limit=10, offset=0, calculation_type=None, is_favorite=None): + # 获取历史记录 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + query = 'SELECT * FROM history ORDER BY timestamp DESC LIMIT ? OFFSET ?' + params = (limit, offset) + + if calculation_type: + query = 'SELECT * FROM history WHERE calculation_type = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?' + params = (calculation_type, limit, offset) + + if is_favorite is not None: + query = 'SELECT * FROM history WHERE is_favorite = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?' + params = (int(is_favorite), limit, offset) + + cursor.execute(query, params) + rows = cursor.fetchall() + + conn.close() + + return self._format_history_rows(rows) + + def search_history(self, keyword): + # 搜索历史记录 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + query = ''' + SELECT * FROM history + WHERE tool_name LIKE ? OR input_data LIKE ? OR output_data LIKE ? + ORDER BY timestamp DESC + ''' + params = (f'%{keyword}%', f'%{keyword}%', f'%{keyword}%') + + cursor.execute(query, params) + rows = cursor.fetchall() + + conn.close() + + return self._format_history_rows(rows) + + def favorite_history(self, history_id, is_favorite=True): + # 收藏/取消收藏历史记录 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute(''' + UPDATE history SET is_favorite = ? WHERE id = ? + ''', (int(is_favorite), history_id)) + + conn.commit() + conn.close() + + def delete_history(self, history_id): + # 删除历史记录 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('DELETE FROM history WHERE id = ?', (history_id,)) + + conn.commit() + conn.close() + + def clear_history(self): + # 清空历史记录 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('DELETE FROM history') + + conn.commit() + conn.close() + + def get_recent_history(self, limit=3): + # 获取最近的历史记录 + return self.get_history(limit=limit) + + def add_project(self, name, description='', data=None): + # 添加项目 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + data_json = json.dumps(data) if data else None + + cursor.execute(''' + INSERT INTO projects (name, description, data) + VALUES (?, ?, ?) + ''', (name, description, data_json)) + + conn.commit() + conn.close() + + def get_projects(self): + # 获取所有项目 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('SELECT * FROM projects ORDER BY created_at DESC') + rows = cursor.fetchall() + + conn.close() + + return self._format_project_rows(rows) + + def get_project(self, project_id): + # 获取单个项目 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('SELECT * FROM projects WHERE id = ?', (project_id,)) + row = cursor.fetchone() + + conn.close() + + return self._format_project_row(row) if row else None + + def update_project(self, project_id, name=None, description=None, data=None): + # 更新项目 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + if name is not None: + cursor.execute('UPDATE projects SET name = ? WHERE id = ?', (name, project_id)) + + if description is not None: + cursor.execute('UPDATE projects SET description = ? WHERE id = ?', (description, project_id)) + + if data is not None: + data_json = json.dumps(data) + cursor.execute('UPDATE projects SET data = ? WHERE id = ?', (data_json, project_id)) + + cursor.execute('UPDATE projects SET updated_at = CURRENT_TIMESTAMP WHERE id = ?', (project_id,)) + + conn.commit() + conn.close() + + def delete_project(self, project_id): + # 删除项目 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('DELETE FROM projects WHERE id = ?', (project_id,)) + + conn.commit() + conn.close() + + def export_history(self, format_type, file_path, history_ids=None): + # 导出历史记录 + if history_ids: + history = self.get_history_by_ids(history_ids) + else: + history = self.get_history(limit=1000) + + if format_type == 'csv': + self._export_history_to_csv(history, file_path) + elif format_type == 'json': + self._export_history_to_json(history, file_path) + elif format_type == 'txt': + self._export_history_to_txt(history, file_path) + else: + raise ValueError(f'不支持的导出格式: {format_type}') + + def export_project(self, project_id, format_type, file_path): + # 导出项目 + project = self.get_project(project_id) + if not project: + raise ValueError(f'找不到项目: {project_id}') + + if format_type == 'csv': + self._export_project_to_csv(project, file_path) + elif format_type == 'json': + self._export_project_to_json(project, file_path) + elif format_type == 'txt': + self._export_project_to_txt(project, file_path) + else: + raise ValueError(f'不支持的导出格式: {format_type}') + + def get_history_by_ids(self, history_ids): + # 根据ID获取历史记录 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + placeholders = ','.join('?' for _ in history_ids) + query = f'SELECT * FROM history WHERE id IN ({placeholders}) ORDER BY timestamp DESC' + + cursor.execute(query, history_ids) + rows = cursor.fetchall() + + conn.close() + + return self._format_history_rows(rows) + + def _format_history_rows(self, rows): + # 格式化历史记录行 + return [self._format_history_row(row) for row in rows] + + def _format_history_row(self, row): + # 格式化历史记录行 + return { + 'id': row[0], + 'tool_name': row[1], + 'input_data': row[2], + 'output_data': row[3], + 'calculation_type': row[4], + 'timestamp': row[5], + 'is_favorite': bool(row[6]) + } + + def _format_project_rows(self, rows): + # 格式化项目行 + return [self._format_project_row(row) for row in rows] + + def _format_project_row(self, row): + # 格式化项目行 + return { + 'id': row[0], + 'name': row[1], + 'description': row[2], + 'created_at': row[3], + 'updated_at': row[4], + 'data': json.loads(row[5]) if row[5] else None + } + + def _export_history_to_csv(self, history, file_path): + # 导出历史记录到CSV + with open(file_path, 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerow(['ID', '工具名称', '输入数据', '输出数据', '计算类型', '时间戳', '是否收藏']) + + for item in history: + writer.writerow([ + item['id'], + item['tool_name'], + item['input_data'], + item['output_data'], + item['calculation_type'], + item['timestamp'], + '是' if item['is_favorite'] else '否' + ]) + + def _export_history_to_json(self, history, file_path): + # 导出历史记录到JSON + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(history, f, ensure_ascii=False, indent=2, default=str) + + def _export_history_to_txt(self, history, file_path): + # 导出历史记录到TXT + with open(file_path, 'w', encoding='utf-8') as f: + for item in history: + f.write(f"ID: {item['id']}\n") + f.write(f"工具名称: {item['tool_name']}\n") + f.write(f"输入数据: {item['input_data']}\n") + f.write(f"输出数据: {item['output_data']}\n") + f.write(f"计算类型: {item['calculation_type']}\n") + f.write(f"时间戳: {item['timestamp']}\n") + f.write(f"是否收藏: {'是' if item['is_favorite'] else '否'}\n") + f.write("\n" + "="*50 + "\n\n") + + def _export_project_to_csv(self, project, file_path): + # 导出项目到CSV + with open(file_path, 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerow(['项目ID', '项目名称', '项目描述', '创建时间', '更新时间']) + writer.writerow([ + project['id'], + project['name'], + project['description'], + project['created_at'], + project['updated_at'] + ]) + + # 如果有数据,写入数据部分 + if project['data']: + writer.writerow([]) + writer.writerow(['项目数据:']) + + # 写入数据的键值对 + for key, value in project['data'].items(): + writer.writerow([key, value]) + + def _export_project_to_json(self, project, file_path): + # 导出项目到JSON + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(project, f, ensure_ascii=False, indent=2, default=str) + + def _export_project_to_txt(self, project, file_path): + # 导出项目到TXT + with open(file_path, 'w', encoding='utf-8') as f: + f.write(f"项目ID: {project['id']}\n") + f.write(f"项目名称: {project['name']}\n") + f.write(f"项目描述: {project['description']}\n") + f.write(f"创建时间: {project['created_at']}\n") + f.write(f"更新时间: {project['updated_at']}\n") + + # 如果有数据,写入数据部分 + if project['data']: + f.write("\n" + "="*50 + "\n") + f.write("项目数据:\n") + f.write("="*50 + "\n") + + # 写入数据的键值对 + for key, value in project['data'].items(): + f.write(f"{key}: {value}\n") + + def backup_database(self, backup_path): + # 备份数据库 + conn = sqlite3.connect(self.db_path) + backup_conn = sqlite3.connect(backup_path) + + with backup_conn: + conn.backup(backup_conn) + + conn.close() + backup_conn.close() + + def restore_database(self, backup_path): + # 恢复数据库 + if not os.path.exists(backup_path): + raise FileNotFoundError(f'备份文件不存在: {backup_path}') + + # 关闭所有连接 + conn = sqlite3.connect(self.db_path) + conn.close() + + # 恢复数据库 + os.replace(backup_path, self.db_path) + + def get_statistics(self): + # 获取统计信息 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # 总历史记录数 + cursor.execute('SELECT COUNT(*) FROM history') + total_history = cursor.fetchone()[0] + + # 总项目数 + cursor.execute('SELECT COUNT(*) FROM projects') + total_projects = cursor.fetchone()[0] + + # 收藏的历史记录数 + cursor.execute('SELECT COUNT(*) FROM history WHERE is_favorite = 1') + favorite_history = cursor.fetchone()[0] + + # 按工具统计使用次数 + cursor.execute('SELECT tool_name, COUNT(*) FROM history GROUP BY tool_name ORDER BY COUNT(*) DESC') + tool_usage = cursor.fetchall() + + conn.close() + + return { + 'total_history': total_history, + 'total_projects': total_projects, + 'favorite_history': favorite_history, + 'tool_usage': tool_usage + } \ No newline at end of file diff --git a/knowledge/knowledge_base.py b/knowledge/knowledge_base.py new file mode 100644 index 0000000..7cf1b6b --- /dev/null +++ b/knowledge/knowledge_base.py @@ -0,0 +1,456 @@ +import json +import os +import sqlite3 +from datetime import datetime + +class KnowledgeBase: + def __init__(self): + self.db_path = 'data/knowledge_base.db' + self.init_database() + + def init_database(self): + # 初始化知识库数据库 + os.makedirs(os.path.dirname(self.db_path), exist_ok=True) + + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # 创建知识条目表格 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS knowledge_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + category TEXT NOT NULL, + subcategory TEXT NOT NULL, + title TEXT NOT NULL, + content TEXT NOT NULL, + keywords TEXT, + difficulty INTEGER DEFAULT 1, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + views INTEGER DEFAULT 0 + ) + ''') + + # 创建学习进度表格 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS learning_progress ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT DEFAULT 'default', + knowledge_id INTEGER NOT NULL, + completed INTEGER DEFAULT 0, + progress INTEGER DEFAULT 0, + last_accessed DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (knowledge_id) REFERENCES knowledge_items (id) + ) + ''') + + # 创建练习表格 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS exercises ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + knowledge_id INTEGER NOT NULL, + question TEXT NOT NULL, + options TEXT NOT NULL, + correct_answer INTEGER NOT NULL, + explanation TEXT, + difficulty INTEGER DEFAULT 1, + FOREIGN KEY (knowledge_id) REFERENCES knowledge_items (id) + ) + ''') + + conn.commit() + conn.close() + + # 初始化基础数据 + self.init_base_data() + + def init_base_data(self): + # 检查是否已有数据 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('SELECT COUNT(*) FROM knowledge_items') + count = cursor.fetchone()[0] + + if count == 0: + # 导入基础知识库数据 + base_data = self._load_base_knowledge() + for item in base_data: + cursor.execute(''' + INSERT INTO knowledge_items (category, subcategory, title, content, keywords, difficulty) + VALUES (?, ?, ?, ?, ?, ?) + ''', (item['category'], item['subcategory'], item['title'], item['content'], item['keywords'], item['difficulty'])) + + conn.commit() + + conn.close() + + def _load_base_knowledge(self): + # 加载基础知识库数据 + return [ + { + 'category': '基础理论', + 'subcategory': '射频基础知识', + 'title': '射频波的特性', + 'content': '射频波是一种电磁波,频率范围通常在3 kHz到300 GHz之间。射频波具有以下特性:\n\n1. **传播特性**:射频波可以通过空气、真空或介质传播,具有反射、折射、绕射和散射等特性。\n\n2. **频率与波长**:频率(f)和波长(λ)的关系为λ = c/f,其中c是光速(约3×10^8 m/s)。\n\n3. **功率与能量**:射频信号的功率通常用分贝毫瓦(dBm)表示,1 dBm = 10^(-3) W。\n\n4. **阻抗匹配**:在射频系统中,阻抗匹配非常重要,不匹配会导致信号反射和功率损失。\n\n5. **传输线效应**:当传输线长度与信号波长可比拟时,会出现传输线效应,需要使用传输线理论分析。', + 'keywords': '射频波,电磁波,频率,波长,功率,阻抗匹配,传输线', + 'difficulty': 1 + }, + { + 'category': '基础理论', + 'subcategory': '传输线理论', + 'title': '传输线的基本参数', + 'content': '传输线的基本参数包括:\n\n1. **特性阻抗(Z0)**:传输线在无限长时的输入阻抗,对于均匀传输线,Z0 = √(L/C),其中L是单位长度电感,C是单位长度电容。\n\n2. **传播常数(γ)**:表示信号在传输线上的衰减和相位变化,γ = α + jβ,其中α是衰减常数(Np/m),β是相位常数(rad/m)。\n\n3. **衰减常数(α)**:表示信号在传输线上的功率衰减,单位为dB/m或Np/m。\n\n4. **相位常数(β)**:表示信号在传输线上的相位变化,β = 2π/λg,其中λg是传输线的波长。\n\n5. **群速(vg)**:信号包络的传播速度,vg = ω/β,其中ω是角频率。', + 'keywords': '传输线,特性阻抗,传播常数,衰减常数,相位常数,群速', + 'difficulty': 2 + }, + { + 'category': '器件知识', + 'subcategory': '天线', + 'title': '天线的基本参数', + 'content': '天线的基本参数包括:\n\n1. **增益(G)**:天线将输入功率转换为定向辐射功率的能力,通常用dBi表示(相对于各向同性天线)。\n\n2. **方向性(D)**:天线在最大辐射方向上的辐射强度与平均辐射强度的比值。\n\n3. **波束宽度**:天线辐射方向图中,功率下降3 dB(半功率点)时的夹角,包括水平波束宽度和垂直波束宽度。\n\n4. **输入阻抗(Zin)**:天线输入端的阻抗,通常需要与传输线阻抗匹配。\n\n5. **驻波比(VSWR)**:表示天线与传输线之间的匹配程度,VSWR = (1 + Γ)/(1 - Γ),其中Γ是反射系数。\n\n6. **带宽(BW)**:天线性能保持在指定范围内的频率范围。', + 'keywords': '天线,增益,方向性,波束宽度,输入阻抗,驻波比,带宽', + 'difficulty': 2 + }, + { + 'category': '系统设计', + 'subcategory': '链路预算', + 'title': '射频链路预算的基本概念', + 'content': '射频链路预算是分析射频系统中信号从发射端到接收端的功率变化和信噪比(SNR)的过程。链路预算的主要组成部分包括:\n\n1. **发射机部分**:发射功率、发射天线增益、馈线损耗。\n\n2. **传播部分**:自由空间损耗、大气损耗、多径损耗、阴影损耗。\n\n3. **接收机部分**:接收天线增益、馈线损耗、接收机噪声系数、接收机灵敏度。\n\n链路预算的目的是确保接收端的信号功率大于接收机灵敏度,并有足够的余量。常用的链路余量计算公式为:\n\n链路余量 = 发射功率 + 发射天线增益 - 馈线损耗 - 自由空间损耗 - 其他损耗 + 接收天线增益 - 接收馈线损耗 - 接收机灵敏度\n\n链路余量通常需要大于10 dB,以保证系统的可靠性。', + 'keywords': '链路预算,发射功率,接收灵敏度,自由空间损耗,噪声系数,链路余量', + 'difficulty': 2 + }, + { + 'category': '标准规范', + 'subcategory': '频率分配', + 'title': '常用射频频段分配', + 'content': '国际电信联盟(ITU)对射频频段进行了统一分配,常用频段包括:\n\n1. **HF(高频)**:3-30 MHz,用于短波通信。\n\n2. **VHF(甚高频)**:30-300 MHz,用于电视、调频广播、航空通信。\n\n3. **UHF(特高频)**:300 MHz-3 GHz,用于手机、卫星通信、雷达。\n\n4. **L波段**:1-2 GHz,用于GPS、卫星通信。\n\n5. **S波段**:2-4 GHz,用于雷达、卫星通信。\n\n6. **C波段**:4-8 GHz,用于卫星通信、雷达。\n\n7. **X波段**:8-12 GHz,用于雷达、卫星通信。\n\n8. **Ku波段**:12-18 GHz,用于卫星电视、卫星通信。\n\n9. **K波段**:18-27 GHz,用于雷达、卫星通信。\n\n10. **Ka波段**:27-40 GHz,用于卫星通信、毫米波通信。', + 'keywords': '频段分配,HF,VHF,UHF,L波段,S波段,C波段,X波段,Ku波段,Ka波段', + 'difficulty': 1 + } + ] + + def get_categories(self): + # 获取所有分类 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('SELECT DISTINCT category FROM knowledge_items ORDER BY category') + categories = [row[0] for row in cursor.fetchall()] + + conn.close() + + return categories + + def get_subcategories(self, category): + # 获取指定分类下的所有子分类 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('SELECT DISTINCT subcategory FROM knowledge_items WHERE category = ? ORDER BY subcategory', (category,)) + subcategories = [row[0] for row in cursor.fetchall()] + + conn.close() + + return subcategories + + def get_knowledge_items(self, category=None, subcategory=None, difficulty=None, keywords=None): + # 获取知识条目 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + query = 'SELECT * FROM knowledge_items ORDER BY category, subcategory, title' + params = [] + + if category: + query = 'SELECT * FROM knowledge_items WHERE category = ? ORDER BY subcategory, title' + params = [category] + + if subcategory: + if category: + query = 'SELECT * FROM knowledge_items WHERE category = ? AND subcategory = ? ORDER BY title' + params = [category, subcategory] + else: + query = 'SELECT * FROM knowledge_items WHERE subcategory = ? ORDER BY category, title' + params = [subcategory] + + if difficulty: + if category and subcategory: + query = 'SELECT * FROM knowledge_items WHERE category = ? AND subcategory = ? AND difficulty = ? ORDER BY title' + params = [category, subcategory, difficulty] + elif category: + query = 'SELECT * FROM knowledge_items WHERE category = ? AND difficulty = ? ORDER BY subcategory, title' + params = [category, difficulty] + elif subcategory: + query = 'SELECT * FROM knowledge_items WHERE subcategory = ? AND difficulty = ? ORDER BY category, title' + params = [subcategory, difficulty] + else: + query = 'SELECT * FROM knowledge_items WHERE difficulty = ? ORDER BY category, subcategory, title' + params = [difficulty] + + if keywords: + keyword_list = keywords.split() + if keyword_list: + where_clauses = [] + for keyword in keyword_list: + where_clauses.append(f'(title LIKE ? OR content LIKE ? OR keywords LIKE ?)') + + keyword_where = ' AND '.join(where_clauses) + + if 'WHERE' in query: + query = f'{query} AND {keyword_where}' + else: + query = f'{query} WHERE {keyword_where}' + + # 为每个关键词添加三个参数(标题、内容、关键词) + for keyword in keyword_list: + params.extend([f'%{keyword}%', f'%{keyword}%', f'%{keyword}%']) + + cursor.execute(query, params) + rows = cursor.fetchall() + + conn.close() + + return self._format_knowledge_rows(rows) + + def get_knowledge_item(self, item_id): + # 获取单个知识条目 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('SELECT * FROM knowledge_items WHERE id = ?', (item_id,)) + row = cursor.fetchone() + + if row: + # 更新浏览次数 + cursor.execute('UPDATE knowledge_items SET views = views + 1 WHERE id = ?', (item_id,)) + conn.commit() + + conn.close() + + return self._format_knowledge_row(row) if row else None + + def search_knowledge(self, keyword): + # 搜索知识库 + return self.get_knowledge_items(keywords=keyword) + + def get_learning_progress(self, user_id='default'): + # 获取学习进度 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute(''' + SELECT k.*, lp.completed, lp.progress, lp.last_accessed + FROM knowledge_items k + LEFT JOIN learning_progress lp ON k.id = lp.knowledge_id AND lp.user_id = ? + ORDER BY k.category, k.subcategory, k.title + ''', (user_id,)) + rows = cursor.fetchall() + + conn.close() + + progress = [] + for row in rows: + progress.append({ + 'id': row[0], + 'category': row[1], + 'subcategory': row[2], + 'title': row[3], + 'completed': bool(row[10]) if row[10] is not None else False, + 'progress': row[11] if row[11] is not None else 0, + 'last_accessed': row[12] + }) + + return progress + + def update_learning_progress(self, knowledge_id, completed=False, progress=0, user_id='default'): + # 更新学习进度 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # 检查是否已有记录 + cursor.execute(''' + SELECT id FROM learning_progress + WHERE knowledge_id = ? AND user_id = ? + ''', (knowledge_id, user_id)) + existing = cursor.fetchone() + + if existing: + # 更新现有记录 + cursor.execute(''' + UPDATE learning_progress + SET completed = ?, progress = ?, last_accessed = CURRENT_TIMESTAMP + WHERE id = ? + ''', (int(completed), progress, existing[0])) + else: + # 创建新记录 + cursor.execute(''' + INSERT INTO learning_progress (user_id, knowledge_id, completed, progress) + VALUES (?, ?, ?, ?) + ''', (user_id, knowledge_id, int(completed), progress)) + + conn.commit() + conn.close() + + def get_exercises(self, knowledge_id=None, difficulty=None): + # 获取练习 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + query = 'SELECT * FROM exercises ORDER BY knowledge_id, difficulty' + params = [] + + if knowledge_id: + query = 'SELECT * FROM exercises WHERE knowledge_id = ? ORDER BY difficulty' + params = [knowledge_id] + + if difficulty: + if knowledge_id: + query = 'SELECT * FROM exercises WHERE knowledge_id = ? AND difficulty = ? ORDER BY id' + params = [knowledge_id, difficulty] + else: + query = 'SELECT * FROM exercises WHERE difficulty = ? ORDER BY knowledge_id' + params = [difficulty] + + cursor.execute(query, params) + rows = cursor.fetchall() + + conn.close() + + return self._format_exercise_rows(rows) + + def get_exercise(self, exercise_id): + # 获取单个练习 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('SELECT * FROM exercises WHERE id = ?', (exercise_id,)) + row = cursor.fetchone() + + conn.close() + + return self._format_exercise_row(row) if row else None + + def add_knowledge_item(self, category, subcategory, title, content, keywords=None, difficulty=1): + # 添加知识条目 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute(''' + INSERT INTO knowledge_items (category, subcategory, title, content, keywords, difficulty) + VALUES (?, ?, ?, ?, ?, ?) + ''', (category, subcategory, title, content, keywords, difficulty)) + + conn.commit() + conn.close() + + def update_knowledge_item(self, item_id, category=None, subcategory=None, title=None, content=None, keywords=None, difficulty=None): + # 更新知识条目 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + if category is not None: + cursor.execute('UPDATE knowledge_items SET category = ? WHERE id = ?', (category, item_id)) + + if subcategory is not None: + cursor.execute('UPDATE knowledge_items SET subcategory = ? WHERE id = ?', (subcategory, item_id)) + + if title is not None: + cursor.execute('UPDATE knowledge_items SET title = ? WHERE id = ?', (title, item_id)) + + if content is not None: + cursor.execute('UPDATE knowledge_items SET content = ? WHERE id = ?', (content, item_id)) + + if keywords is not None: + cursor.execute('UPDATE knowledge_items SET keywords = ? WHERE id = ?', (keywords, item_id)) + + if difficulty is not None: + cursor.execute('UPDATE knowledge_items SET difficulty = ? WHERE id = ?', (difficulty, item_id)) + + cursor.execute('UPDATE knowledge_items SET updated_at = CURRENT_TIMESTAMP WHERE id = ?', (item_id,)) + + conn.commit() + conn.close() + + def delete_knowledge_item(self, item_id): + # 删除知识条目 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('DELETE FROM knowledge_items WHERE id = ?', (item_id,)) + cursor.execute('DELETE FROM learning_progress WHERE knowledge_id = ?', (item_id,)) + cursor.execute('DELETE FROM exercises WHERE knowledge_id = ?', (item_id,)) + + conn.commit() + conn.close() + + def _format_knowledge_rows(self, rows): + # 格式化知识条目行 + return [self._format_knowledge_row(row) for row in rows] + + def _format_knowledge_row(self, row): + # 格式化知识条目行 + return { + 'id': row[0], + 'category': row[1], + 'subcategory': row[2], + 'title': row[3], + 'content': row[4], + 'keywords': row[5], + 'difficulty': row[6], + 'created_at': row[7], + 'updated_at': row[8], + 'views': row[9] + } + + def _format_exercise_rows(self, rows): + # 格式化练习行 + return [self._format_exercise_row(row) for row in rows] + + def _format_exercise_row(self, row): + # 格式化练习行 + return { + 'id': row[0], + 'knowledge_id': row[1], + 'question': row[2], + 'options': json.loads(row[3]), + 'correct_answer': row[4], + 'explanation': row[5], + 'difficulty': row[6] + } + + def get_statistics(self): + # 获取知识库统计信息 + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # 总知识条目数 + cursor.execute('SELECT COUNT(*) FROM knowledge_items') + total_items = cursor.fetchone()[0] + + # 分类统计 + cursor.execute('SELECT category, COUNT(*) FROM knowledge_items GROUP BY category ORDER BY COUNT(*) DESC') + category_stats = cursor.fetchall() + + # 难度统计 + cursor.execute('SELECT difficulty, COUNT(*) FROM knowledge_items GROUP BY difficulty ORDER BY difficulty') + difficulty_stats = cursor.fetchall() + + # 总练习数 + cursor.execute('SELECT COUNT(*) FROM exercises') + total_exercises = cursor.fetchone()[0] + + # 学习进度统计 + cursor.execute('SELECT COUNT(*) FROM learning_progress WHERE completed = 1') + completed_items = cursor.fetchone()[0] + + conn.close() + + return { + 'total_items': total_items, + 'category_stats': category_stats, + 'difficulty_stats': difficulty_stats, + 'total_exercises': total_exercises, + 'completed_items': completed_items, + 'completion_rate': (completed_items / total_items) * 100 if total_items > 0 else 0 + } \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..a27c840 --- /dev/null +++ b/main.py @@ -0,0 +1,629 @@ +import sys +import os +from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit, QComboBox, QTextEdit, QTabWidget, QScrollArea, QFrame, QSplitter, QAction, QMenuBar, QMenu, QToolBar, QStatusBar +from PyQt5.QtGui import QIcon, QFont, QPixmap +from PyQt5.QtCore import Qt, QSize, QThread, pyqtSignal +import qdarkstyle +from PyQt5.QtWidgets import QStyleFactory + +# 导入工具模块 +from tools.frequency_wavelength import FrequencyWavelengthCalculator +from tools.power_converter import PowerConverter +from tools.transmission_line import TransmissionLineCalculator +from tools.antenna_calculator import AntennaCalculator +from tools.filter_designer import FilterDesigner +from tools.link_budget import LinkBudgetAnalyzer + +# 导入数据管理模块 +from data.project_manager import ProjectManager +from data.history_manager import HistoryManager + +# 导入知识库模块 +from knowledge.knowledge_base import KnowledgeBase + +# 导入工具函数 +from utils.visualization import plot_wavelength_distribution +from utils.config import ConfigManager + +class RFToolboxProfessional(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("RF Toolbox Professional") + self.setGeometry(100, 100, 1200, 800) + + # 初始化配置管理器 + self.config = ConfigManager() + + # 初始化项目管理器 + self.project_manager = ProjectManager() + + # 初始化历史管理器 + self.history_manager = HistoryManager() + + # 初始化知识库 + self.knowledge_base = KnowledgeBase() + + # 最近使用的工具 + self.recent_tools = self.history_manager.get_recent_tools(3) + + # 创建菜单和工具栏 + self.create_menu_bar() + self.create_tool_bar() + + # 创建主布局 + self.create_main_layout() + + # 应用主题 + self.apply_theme() + + # 显示首页 + self.show_home() + + def create_menu_bar(self): + # 创建菜单栏 + menubar = self.menuBar() + + # 文件菜单 + file_menu = menubar.addMenu('&文件') + + # 新建项目 + new_project_action = QAction('新建项目', self) + new_project_action.triggered.connect(self.project_manager.new_project) + file_menu.addAction(new_project_action) + + # 打开项目 + open_project_action = QAction('打开项目', self) + open_project_action.triggered.connect(self.project_manager.open_project) + file_menu.addAction(open_project_action) + + # 保存项目 + save_project_action = QAction('保存项目', self) + save_project_action.triggered.connect(self.project_manager.save_project) + file_menu.addAction(save_project_action) + + # 导出项目 + export_menu = file_menu.addMenu('导出项目') + export_csv_action = QAction('导出为CSV', self) + export_csv_action.triggered.connect(lambda: self.project_manager.export_project('csv')) + export_menu.addAction(export_csv_action) + + export_json_action = QAction('导出为JSON', self) + export_json_action.triggered.connect(lambda: self.project_manager.export_project('json')) + export_menu.addAction(export_json_action) + + export_pdf_action = QAction('导出为PDF', self) + export_pdf_action.triggered.connect(lambda: self.project_manager.export_project('pdf')) + export_menu.addAction(export_pdf_action) + + # 分隔线 + file_menu.addSeparator() + + # 退出 + exit_action = QAction('退出', self) + exit_action.triggered.connect(self.close) + file_menu.addAction(exit_action) + + # 工具菜单 + tools_menu = menubar.addMenu('&工具') + + # 频率-波长计算器 + freq_wave_action = QAction('频率-波长计算器', self) + freq_wave_action.triggered.connect(self.show_freq_wavelength_calculator) + tools_menu.addAction(freq_wave_action) + + # 功率单位换算 + power_conv_action = QAction('功率单位换算', self) + power_conv_action.triggered.connect(self.show_power_converter) + tools_menu.addAction(power_conv_action) + + # 传输线计算 + trans_line_action = QAction('传输线计算', self) + trans_line_action.triggered.connect(self.show_transmission_line_calculator) + tools_menu.addAction(trans_line_action) + + # 天线参数计算器 + antenna_action = QAction('天线参数计算器', self) + antenna_action.triggered.connect(self.show_antenna_calculator) + tools_menu.addAction(antenna_action) + + # 滤波器设计工具 + filter_action = QAction('滤波器设计工具', self) + filter_action.triggered.connect(self.show_filter_designer) + tools_menu.addAction(filter_action) + + # 射频链路预算分析 + link_budget_action = QAction('射频链路预算分析', self) + link_budget_action.triggered.connect(self.show_link_budget_analyzer) + tools_menu.addAction(link_budget_action) + + # 视图菜单 + view_menu = menubar.addMenu('&视图') + + # 主题切换 + theme_menu = view_menu.addMenu('主题') + + # 浅色主题 + light_theme_action = QAction('浅色主题', self) + light_theme_action.triggered.connect(lambda: self.set_theme('light')) + theme_menu.addAction(light_theme_action) + + # 深色主题 + dark_theme_action = QAction('深色主题', self) + dark_theme_action.triggered.connect(lambda: self.set_theme('dark')) + theme_menu.addAction(dark_theme_action) + + # 跟随系统 + system_theme_action = QAction('跟随系统', self) + system_theme_action.triggered.connect(lambda: self.set_theme('system')) + theme_menu.addAction(system_theme_action) + + # 帮助菜单 + help_menu = menubar.addMenu('&帮助') + + # 内置教程 + tutorial_action = QAction('内置教程', self) + tutorial_action.triggered.connect(self.show_tutorial) + help_menu.addAction(tutorial_action) + + # 常见问题 + faq_action = QAction('常见问题', self) + faq_action.triggered.connect(self.show_faq) + help_menu.addAction(faq_action) + + # 关于 + about_action = QAction('关于', self) + about_action.triggered.connect(self.show_about) + help_menu.addAction(about_action) + + def create_tool_bar(self): + # 创建工具栏 + toolbar = self.addToolBar('工具栏') + toolbar.setIconSize(QSize(16, 16)) + + # 新建项目 + new_project_action = QAction(QIcon('resources/icons/new_project.png'), '新建项目', self) + new_project_action.triggered.connect(self.project_manager.new_project) + toolbar.addAction(new_project_action) + + # 打开项目 + open_project_action = QAction(QIcon('resources/icons/open_project.png'), '打开项目', self) + open_project_action.triggered.connect(self.project_manager.open_project) + toolbar.addAction(open_project_action) + + # 保存项目 + save_project_action = QAction(QIcon('resources/icons/save_project.png'), '保存项目', self) + save_project_action.triggered.connect(self.project_manager.save_project) + toolbar.addAction(save_project_action) + + toolbar.addSeparator() + + # 频率-波长计算器 + freq_wave_action = QAction(QIcon('resources/icons/frequency.png'), '频率-波长计算器', self) + freq_wave_action.triggered.connect(self.show_freq_wavelength_calculator) + toolbar.addAction(freq_wave_action) + + # 功率单位换算 + power_conv_action = QAction(QIcon('resources/icons/power.png'), '功率单位换算', self) + power_conv_action.triggered.connect(self.show_power_converter) + toolbar.addAction(power_conv_action) + + # 传输线计算 + trans_line_action = QAction(QIcon('resources/icons/transmission_line.png'), '传输线计算', self) + trans_line_action.triggered.connect(self.show_transmission_line_calculator) + toolbar.addAction(trans_line_action) + + # 天线参数计算器 + antenna_action = QAction(QIcon('resources/icons/antenna.png'), '天线参数计算器', self) + antenna_action.triggered.connect(self.show_antenna_calculator) + toolbar.addAction(antenna_action) + + # 滤波器设计工具 + filter_action = QAction(QIcon('resources/icons/filter.png'), '滤波器设计工具', self) + filter_action.triggered.connect(self.show_filter_designer) + toolbar.addAction(filter_action) + + # 射频链路预算分析 + link_budget_action = QAction(QIcon('resources/icons/link_budget.png'), '射频链路预算分析', self) + link_budget_action.triggered.connect(self.show_link_budget_analyzer) + toolbar.addAction(link_budget_action) + + toolbar.addSeparator() + + # 主题切换 + theme_action = QAction(QIcon('resources/icons/theme.png'), '主题切换', self) + theme_menu = QMenu(self) + + light_theme_action = QAction('浅色主题', self) + light_theme_action.triggered.connect(lambda: self.set_theme('light')) + theme_menu.addAction(light_theme_action) + + dark_theme_action = QAction('深色主题', self) + dark_theme_action.triggered.connect(lambda: self.set_theme('dark')) + theme_menu.addAction(dark_theme_action) + + system_theme_action = QAction('跟随系统', self) + system_theme_action.triggered.connect(lambda: self.set_theme('system')) + theme_menu.addAction(system_theme_action) + + theme_action.setMenu(theme_menu) + toolbar.addAction(theme_action) + + def create_main_layout(self): + # 创建中心部件 + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # 创建主布局 + main_layout = QHBoxLayout(central_widget) + + # 创建左侧导航栏 + self.nav_frame = QFrame() + self.nav_frame.setFixedWidth(200) + self.nav_frame.setFrameShape(QFrame.StyledPanel) + + # 创建导航布局 + nav_layout = QVBoxLayout(self.nav_frame) + + # 创建logo + logo_label = QLabel('RF Toolbox') + logo_label.setFont(QFont('Consolas', 16, QFont.Bold)) + logo_label.setAlignment(Qt.AlignCenter) + logo_label.setStyleSheet('padding: 10px; margin-bottom: 20px;') + nav_layout.addWidget(logo_label) + + # 创建导航按钮 + self.create_nav_buttons(nav_layout) + + # 创建搜索框 + search_frame = QFrame() + search_layout = QHBoxLayout(search_frame) + search_label = QLabel('搜索:') + self.search_edit = QLineEdit() + self.search_edit.textChanged.connect(self.search_tools) + search_layout.addWidget(search_label) + search_layout.addWidget(self.search_edit) + nav_layout.addWidget(search_frame) + + # 填充剩余空间 + nav_layout.addStretch() + + # 创建右侧内容区 + self.content_frame = QFrame() + + # 创建内容布局 + content_layout = QVBoxLayout(self.content_frame) + + # 创建面包屑导航 + self.breadcrumb_label = QLabel('首页') + self.breadcrumb_label.setFont(QFont('Consolas', 10)) + content_layout.addWidget(self.breadcrumb_label) + + # 创建内容显示区 + self.content_display = QWidget() + content_layout.addWidget(self.content_display) + + # 添加到主布局 + main_layout.addWidget(self.nav_frame) + main_layout.addWidget(self.content_frame) + + def create_nav_buttons(self, layout): + # 导航按钮数据 + nav_buttons = [ + ('首页', 'home', self.show_home), + ('频率-波长计算', 'frequency', self.show_freq_wavelength_calculator), + ('功率单位换算', 'power', self.show_power_converter), + ('传输线计算', 'transmission_line', self.show_transmission_line_calculator), + ('天线参数计算', 'antenna', self.show_antenna_calculator), + ('滤波器设计', 'filter', self.show_filter_designer), + ('链路预算分析', 'link_budget', self.show_link_budget_analyzer), + ('知识库', 'knowledge', self.show_knowledge_base), + ('计算历史', 'history', self.show_history), + ('项目管理', 'project', self.show_project_manager) + ] + + # 创建按钮 + self.nav_buttons = [] + for text, icon_name, callback in nav_buttons: + button = QPushButton(text) + button.setIcon(QIcon(f'resources/icons/{icon_name}.png')) + button.setIconSize(QSize(16, 16)) + button.setStyleSheet('text-align: left; padding: 10px;') + button.clicked.connect(callback) + layout.addWidget(button) + self.nav_buttons.append(button) + + def show_home(self): + # 更新面包屑 + self.breadcrumb_label.setText('首页') + + # 清空内容区 + self.clear_content() + + # 创建首页布局 + home_layout = QVBoxLayout(self.content_display) + + # 创建标题 + title_label = QLabel('RF Toolbox Professional') + title_label.setFont(QFont('Consolas', 24, QFont.Bold)) + title_label.setAlignment(Qt.AlignCenter) + home_layout.addWidget(title_label) + + # 创建描述 + desc_label = QLabel('覆盖射频工程全流程的专业设计平台') + desc_label.setFont(QFont('Consolas', 12)) + desc_label.setAlignment(Qt.AlignCenter) + home_layout.addWidget(desc_label) + + # 最近使用的工具 + if self.recent_tools: + recent_label = QLabel('最近使用的工具') + recent_label.setFont(QFont('Consolas', 14, QFont.Bold)) + home_layout.addWidget(recent_label) + + recent_frame = QFrame() + recent_layout = QVBoxLayout(recent_frame) + + for tool_name, tool_icon, tool_callback in self.recent_tools: + tool_button = QPushButton(tool_name) + tool_button.setIcon(QIcon(f'resources/icons/{tool_icon}.png')) + tool_button.setIconSize(QSize(16, 16)) + tool_button.setStyleSheet('padding: 10px; margin: 5px 0;') + tool_button.clicked.connect(tool_callback) + recent_layout.addWidget(tool_button) + + home_layout.addWidget(recent_frame) + + # 常用工具 + tools_label = QLabel('常用工具') + tools_label.setFont(QFont('Consolas', 14, QFont.Bold)) + home_layout.addWidget(tools_label) + + tools_frame = QFrame() + tools_layout = QGridLayout(tools_frame) + + # 常用工具数据 + common_tools = [ + ('频率-波长计算', 'frequency', self.show_freq_wavelength_calculator), + ('功率单位换算', 'power', self.show_power_converter), + ('传输线计算', 'transmission_line', self.show_transmission_line_calculator), + ('天线参数计算', 'antenna', self.show_antenna_calculator), + ('滤波器设计', 'filter', self.show_filter_designer), + ('链路预算分析', 'link_budget', self.show_link_budget_analyzer) + ] + + # 创建工具按钮 + for i, (text, icon_name, callback) in enumerate(common_tools): + tool_button = QPushButton(text) + tool_button.setIcon(QIcon(f'resources/icons/{icon_name}.png')) + tool_button.setIconSize(QSize(16, 16)) + tool_button.setStyleSheet('padding: 15px;') + tool_button.clicked.connect(callback) + tools_layout.addWidget(tool_button, i//2, i%2) + + home_layout.addWidget(tools_frame) + + # 填充剩余空间 + home_layout.addStretch() + + def show_freq_wavelength_calculator(self): + # 更新面包屑 + self.breadcrumb_label.setText('首页 > 频率-波长计算器') + + # 清空内容区 + self.clear_content() + + # 创建频率-波长计算器 + calculator = FrequencyWavelengthCalculator(self.content_display) + + # 添加到内容区 + layout = QVBoxLayout(self.content_display) + layout.addWidget(calculator) + + # 记录到最近使用 + self.history_manager.add_recent_tool('频率-波长计算器', 'frequency', self.show_freq_wavelength_calculator) + + def show_power_converter(self): + # 更新面包屑 + self.breadcrumb_label.setText('首页 > 功率单位换算') + + # 清空内容区 + self.clear_content() + + # 创建功率单位换算器 + converter = PowerConverter(self.content_display) + + # 添加到内容区 + layout = QVBoxLayout(self.content_display) + layout.addWidget(converter) + + # 记录到最近使用 + self.history_manager.add_recent_tool('功率单位换算', 'power', self.show_power_converter) + + def show_transmission_line_calculator(self): + # 更新面包屑 + self.breadcrumb_label.setText('首页 > 传输线计算') + + # 清空内容区 + self.clear_content() + + # 创建传输线计算器 + calculator = TransmissionLineCalculator(self.content_display) + + # 添加到内容区 + layout = QVBoxLayout(self.content_display) + layout.addWidget(calculator) + + # 记录到最近使用 + self.history_manager.add_recent_tool('传输线计算', 'transmission_line', self.show_transmission_line_calculator) + + def show_antenna_calculator(self): + # 更新面包屑 + self.breadcrumb_label.setText('首页 > 天线参数计算器') + + # 清空内容区 + self.clear_content() + + # 创建天线参数计算器 + calculator = AntennaCalculator(self.content_display) + + # 添加到内容区 + layout = QVBoxLayout(self.content_display) + layout.addWidget(calculator) + + # 记录到最近使用 + self.history_manager.add_recent_tool('天线参数计算器', 'antenna', self.show_antenna_calculator) + + def show_filter_designer(self): + # 更新面包屑 + self.breadcrumb_label.setText('首页 > 滤波器设计工具') + + # 清空内容区 + self.clear_content() + + # 创建滤波器设计工具 + designer = FilterDesigner(self.content_display) + + # 添加到内容区 + layout = QVBoxLayout(self.content_display) + layout.addWidget(designer) + + # 记录到最近使用 + self.history_manager.add_recent_tool('滤波器设计工具', 'filter', self.show_filter_designer) + + def show_link_budget_analyzer(self): + # 更新面包屑 + self.breadcrumb_label.setText('首页 > 射频链路预算分析') + + # 清空内容区 + self.clear_content() + + # 创建射频链路预算分析器 + analyzer = LinkBudgetAnalyzer(self.content_display) + + # 添加到内容区 + layout = QVBoxLayout(self.content_display) + layout.addWidget(analyzer) + + # 记录到最近使用 + self.history_manager.add_recent_tool('射频链路预算分析', 'link_budget', self.show_link_budget_analyzer) + + def show_knowledge_base(self): + # 更新面包屑 + self.breadcrumb_label.setText('首页 > 知识库') + + # 清空内容区 + self.clear_content() + + # 创建知识库界面 + knowledge_widget = self.knowledge_base.get_widget(self.content_display) + + # 添加到内容区 + layout = QVBoxLayout(self.content_display) + layout.addWidget(knowledge_widget) + + # 记录到最近使用 + self.history_manager.add_recent_tool('知识库', 'knowledge', self.show_knowledge_base) + + def show_history(self): + # 更新面包屑 + self.breadcrumb_label.setText('首页 > 计算历史') + + # 清空内容区 + self.clear_content() + + # 创建历史记录界面 + history_widget = self.history_manager.get_widget(self.content_display) + + # 添加到内容区 + layout = QVBoxLayout(self.content_display) + layout.addWidget(history_widget) + + # 记录到最近使用 + self.history_manager.add_recent_tool('计算历史', 'history', self.show_history) + + def show_project_manager(self): + # 更新面包屑 + self.breadcrumb_label.setText('首页 > 项目管理') + + # 清空内容区 + self.clear_content() + + # 创建项目管理界面 + project_widget = self.project_manager.get_widget(self.content_display) + + # 添加到内容区 + layout = QVBoxLayout(self.content_display) + layout.addWidget(project_widget) + + # 记录到最近使用 + self.history_manager.add_recent_tool('项目管理', 'project', self.show_project_manager) + + def show_tutorial(self): + # 显示内置教程 + pass + + def show_faq(self): + # 显示常见问题 + pass + + def show_about(self): + # 显示关于信息 + pass + + def clear_content(self): + # 清空内容区 + for widget in self.content_display.findChildren(QWidget): + widget.deleteLater() + + def search_tools(self, text): + # 搜索工具 + pass + + def set_theme(self, theme): + # 设置主题 + self.config.set_theme(theme) + self.apply_theme() + + def apply_theme(self): + # 应用主题 + theme = self.config.get_theme() + + if theme == 'dark': + app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) + else: + app.setStyleSheet('') + + if theme == 'system': + # 跟随系统主题 + if sys.platform == 'darwin': + # macOS + if app.style().objectName().lower().contains('dark'): + app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) + else: + app.setStyleSheet('') + elif sys.platform == 'win32': + # Windows + import ctypes + try: + ctypes.windll.dwmapi.DwmIsCompositionEnabled() + if ctypes.windll.uxtheme.GetCurrentThemeName(None, 0, None, 0) == 0: + # Windows 10/11 深色主题 + app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) + else: + app.setStyleSheet('') + except: + app.setStyleSheet('') + +if __name__ == '__main__': + app = QApplication(sys.argv) + app.setApplicationName('RF Toolbox Professional') + + # 设置默认字体 + font = QFont('Consolas', 10) + app.setFont(font) + + # 创建主窗口 + main_window = RFToolboxProfessional() + main_window.show() + + sys.exit(app.exec_()) \ No newline at end of file diff --git a/tools/antenna_calculator.py b/tools/antenna_calculator.py new file mode 100644 index 0000000..dfc5c53 --- /dev/null +++ b/tools/antenna_calculator.py @@ -0,0 +1,802 @@ +import sys +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QComboBox, QTableWidget, QTableWidgetItem, QTextEdit, QGroupBox, QGridLayout, QDoubleSpinBox, QMessageBox, QSplitter +from PyQt5.QtGui import QFont, QIcon +from PyQt5.QtCore import Qt +from data.history_manager import HistoryManager + +class AntennaCalculator(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + self.history_manager = HistoryManager() + self.calculation_results = [] + + def init_ui(self): + # 创建主布局 + main_layout = QVBoxLayout(self) + + # 创建标题 + title_label = QLabel('天线参数计算器') + title_label.setFont(QFont('Consolas', 16, QFont.Bold)) + main_layout.addWidget(title_label) + + # 创建输入区域 + input_group = QGroupBox('天线参数') + input_layout = QGridLayout(input_group) + + # 天线类型 + antenna_type_label = QLabel('天线类型:') + self.antenna_type_combo = QComboBox() + self.antenna_type_combo.addItems(['偶极子天线', '贴片天线', '喇叭天线', '八木天线', '抛物面天线']) + self.antenna_type_combo.currentTextChanged.connect(self.update_antenna_params) + input_layout.addWidget(antenna_type_label, 0, 0) + input_layout.addWidget(self.antenna_type_combo, 0, 1) + + # 频率输入 + freq_label = QLabel('频率 (GHz):') + self.freq_edit = QDoubleSpinBox() + self.freq_edit.setRange(0.1, 100.0) + self.freq_edit.setValue(2.4) + input_layout.addWidget(freq_label, 0, 2) + input_layout.addWidget(self.freq_edit, 0, 3) + + # 天线长度 + length_label = QLabel('天线长度:') + self.length_edit = QDoubleSpinBox() + self.length_edit.setRange(0.01, 1000.0) + self.length_edit.setValue(62.5) + self.length_unit_combo = QComboBox() + self.length_unit_combo.addItems(['mm', 'cm', 'm', 'λ']) + length_layout = QHBoxLayout() + length_layout.addWidget(self.length_edit) + length_layout.addWidget(self.length_unit_combo) + input_layout.addWidget(length_label, 1, 0) + input_layout.addLayout(length_layout, 1, 1) + + # 天线宽度 + width_label = QLabel('天线宽度:') + self.width_edit = QDoubleSpinBox() + self.width_edit.setRange(0.01, 1000.0) + self.width_edit.setValue(31.25) + self.width_unit_combo = QComboBox() + self.width_unit_combo.addItems(['mm', 'cm', 'm', 'λ']) + width_layout = QHBoxLayout() + width_layout.addWidget(self.width_edit) + width_layout.addWidget(self.width_unit_combo) + input_layout.addWidget(width_label, 1, 2) + input_layout.addLayout(width_layout, 1, 3) + + # 介电常数 + er_label = QLabel('相对介电常数:') + self.er_edit = QDoubleSpinBox() + self.er_edit.setRange(1.0, 20.0) + self.er_edit.setValue(4.4) + input_layout.addWidget(er_label, 2, 0) + input_layout.addWidget(self.er_edit, 2, 1) + + # 介质厚度 + thickness_label = QLabel('介质厚度:') + self.thickness_edit = QDoubleSpinBox() + self.thickness_edit.setRange(0.01, 100.0) + self.thickness_edit.setValue(1.6) + self.thickness_unit_combo = QComboBox() + self.thickness_unit_combo.addItems(['mm', 'cm', 'm']) + thickness_layout = QHBoxLayout() + thickness_layout.addWidget(self.thickness_edit) + thickness_layout.addWidget(self.thickness_unit_combo) + input_layout.addWidget(thickness_label, 2, 2) + input_layout.addLayout(thickness_layout, 2, 3) + + # 天线效率 + efficiency_label = QLabel('天线效率 (%):') + self.efficiency_edit = QDoubleSpinBox() + self.efficiency_edit.setRange(1.0, 100.0) + self.efficiency_edit.setValue(90.0) + input_layout.addWidget(efficiency_label, 3, 0) + input_layout.addWidget(self.efficiency_edit, 3, 1) + + # 天线增益 + gain_label = QLabel('天线增益 (dBi):') + self.gain_edit = QDoubleSpinBox() + self.gain_edit.setRange(-10.0, 50.0) + self.gain_edit.setValue(2.15) + self.gain_edit.setReadOnly(True) + input_layout.addWidget(gain_label, 3, 2) + input_layout.addWidget(self.gain_edit, 3, 3) + + main_layout.addWidget(input_group) + + # 创建计算按钮区域 + button_layout = QHBoxLayout() + + # 计算按钮 + calc_button = QPushButton('计算天线参数') + calc_button.setIcon(QIcon('resources/icons/calculate.png')) + calc_button.clicked.connect(self.calculate_antenna_params) + button_layout.addWidget(calc_button) + + # 设计天线按钮 + design_button = QPushButton('天线设计优化') + design_button.setIcon(QIcon('resources/icons/design.png')) + design_button.clicked.connect(self.design_antenna) + button_layout.addWidget(design_button) + + # 清除按钮 + clear_button = QPushButton('清除') + clear_button.setIcon(QIcon('resources/icons/clear.png')) + clear_button.clicked.connect(self.clear) + button_layout.addWidget(clear_button) + + main_layout.addLayout(button_layout) + + # 创建结果显示区域 + result_group = QGroupBox('计算结果') + result_layout = QVBoxLayout(result_group) + + # 创建结果表格 + self.result_table = QTableWidget() + self.result_table.setColumnCount(3) + self.result_table.setHorizontalHeaderLabels(['参数名称', '数值', '单位']) + self.result_table.horizontalHeader().setStretchLastSection(True) + result_layout.addWidget(self.result_table) + + # 创建结果详情区域 + self.result_detail = QTextEdit() + self.result_detail.setReadOnly(True) + self.result_detail.setFont(QFont('Consolas', 10)) + result_layout.addWidget(self.result_detail) + + main_layout.addWidget(result_group) + + # 创建可视化区域 + visual_group = QGroupBox('天线辐射方向图') + visual_layout = QVBoxLayout(visual_group) + + # 创建图表画布 + self.figure = plt.figure(figsize=(8, 6)) + self.canvas = FigureCanvas(self.figure) + visual_layout.addWidget(self.canvas) + + # 图表控制按钮 + chart_button_layout = QHBoxLayout() + + # 绘制2D方向图按钮 + plot_2d_button = QPushButton('绘制2D方向图') + plot_2d_button.clicked.connect(self.plot_2d_radiation_pattern) + chart_button_layout.addWidget(plot_2d_button) + + # 绘制3D方向图按钮 + plot_3d_button = QPushButton('绘制3D方向图') + plot_3d_button.clicked.connect(self.plot_3d_radiation_pattern) + chart_button_layout.addWidget(plot_3d_button) + + # 清空图表按钮 + clear_chart_button = QPushButton('清空图表') + clear_chart_button.clicked.connect(self.clear_chart) + chart_button_layout.addWidget(clear_chart_button) + + visual_layout.addLayout(chart_button_layout) + + main_layout.addWidget(visual_group) + + # 填充剩余空间 + main_layout.addStretch() + + def update_antenna_params(self, antenna_type): + # 根据天线类型更新默认参数 + if antenna_type == '偶极子天线': + self.length_edit.setValue(62.5) + self.width_edit.setValue(31.25) + self.er_edit.setValue(1.0) + self.thickness_edit.setValue(0.1) + self.efficiency_edit.setValue(90.0) + self.gain_edit.setValue(2.15) + elif antenna_type == '贴片天线': + self.length_edit.setValue(31.25) + self.width_edit.setValue(41.7) + self.er_edit.setValue(4.4) + self.thickness_edit.setValue(1.6) + self.efficiency_edit.setValue(85.0) + self.gain_edit.setValue(6.5) + elif antenna_type == '喇叭天线': + self.length_edit.setValue(150.0) + self.width_edit.setValue(100.0) + self.er_edit.setValue(1.0) + self.thickness_edit.setValue(10.0) + self.efficiency_edit.setValue(95.0) + self.gain_edit.setValue(15.0) + elif antenna_type == '八木天线': + self.length_edit.setValue(120.0) + self.width_edit.setValue(80.0) + self.er_edit.setValue(1.0) + self.thickness_edit.setValue(1.0) + self.efficiency_edit.setValue(80.0) + self.gain_edit.setValue(12.0) + elif antenna_type == '抛物面天线': + self.length_edit.setValue(1000.0) + self.width_edit.setValue(1000.0) + self.er_edit.setValue(1.0) + self.thickness_edit.setValue(50.0) + self.efficiency_edit.setValue(70.0) + self.gain_edit.setValue(30.0) + + def calculate_antenna_params(self): + # 计算天线参数 + try: + # 获取输入参数 + antenna_type = self.antenna_type_combo.currentText() + freq = self.freq_edit.value() * 1e9 # Hz + length = self.length_edit.value() + length_unit = self.length_unit_combo.currentText() + width = self.width_edit.value() + width_unit = self.width_unit_combo.currentText() + er = self.er_edit.value() + thickness = self.thickness_edit.value() + thickness_unit = self.thickness_unit_combo.currentText() + efficiency = self.efficiency_edit.value() / 100 # 转换为小数 + + # 计算波长 + c0 = 299792458 # 真空中的光速 + lambda0 = c0 / freq # 自由空间波长 + + # 转换长度单位为米 + if length_unit == 'mm': + length_m = length / 1000 + elif length_unit == 'cm': + length_m = length / 100 + elif length_unit == 'm': + length_m = length + elif length_unit == 'λ': + length_m = length * lambda0 + else: + length_m = length / 1000 # 默认mm + + # 转换宽度单位为米 + if width_unit == 'mm': + width_m = width / 1000 + elif width_unit == 'cm': + width_m = width / 100 + elif width_unit == 'm': + width_m = width + elif width_unit == 'λ': + width_m = width * lambda0 + else: + width_m = width / 1000 # 默认mm + + # 转换厚度单位为米 + if thickness_unit == 'mm': + thickness_m = thickness / 1000 + elif thickness_unit == 'cm': + thickness_m = thickness / 100 + elif thickness_unit == 'm': + thickness_m = thickness + else: + thickness_m = thickness / 1000 # 默认mm + + # 根据天线类型计算参数 + if antenna_type == '偶极子天线': + params = self.calculate_dipole_antenna(freq, length_m, efficiency) + elif antenna_type == '贴片天线': + params = self.calculate_patch_antenna(freq, length_m, width_m, er, thickness_m, efficiency) + elif antenna_type == '喇叭天线': + params = self.calculate_horn_antenna(freq, length_m, width_m, efficiency) + elif antenna_type == '八木天线': + params = self.calculate_yagi_antenna(freq, length_m, width_m, efficiency) + elif antenna_type == '抛物面天线': + params = self.calculate_parabolic_antenna(freq, length_m, efficiency) + else: + params = {} + + # 添加通用参数 + params['天线类型'] = antenna_type + params['频率'] = freq + params['自由空间波长'] = lambda0 + params['天线长度'] = length_m + params['天线宽度'] = width_m + params['相对介电常数'] = er + params['介质厚度'] = thickness_m + params['天线效率'] = efficiency + + # 显示结果 + self.display_results(params) + + # 保存到历史 + self.history_manager.add_history( + tool_name='天线参数计算器', + input_data=f"天线类型: {antenna_type}, 频率: {freq/1e9} GHz", + output_data=f"天线增益: {params['天线增益']:.2f} dBi, 波束宽度: {params['波束宽度H']:.2f}° × {params['波束宽度E']:.2f}°", + calculation_type='antenna_parameters' + ) + + except Exception as e: + QMessageBox.warning(self, '警告', f'计算错误: {str(e)}') + + def calculate_dipole_antenna(self, freq, length, efficiency): + # 计算偶极子天线参数 + c0 = 299792458 # 真空中的光速 + lambda0 = c0 / freq # 自由空间波长 + + # 计算天线增益 (dBi) + if length < lambda0 / 2: + # 短偶极子 + gain = 2.15 # dBi + else: + # 半波偶极子 + gain = 2.15 # dBi + + # 计算波束宽度 + beamwidth_h = 78.0 # 水平波束宽度 (度) + beamwidth_e = 78.0 # 垂直波束宽度 (度) + + # 计算有效面积 + effective_area = (lambda0**2 * 10**(gain/10)) / (4 * np.pi) + + # 计算输入阻抗 + input_impedance = 73.1 + 42.5j # Ω + + return { + '天线增益': gain, + '波束宽度H': beamwidth_h, + '波束宽度E': beamwidth_e, + '有效面积': effective_area, + '输入阻抗': input_impedance, + '天线效率': efficiency + } + + def calculate_patch_antenna(self, freq, length, width, er, thickness, efficiency): + # 计算贴片天线参数 + c0 = 299792458 # 真空中的光速 + lambda0 = c0 / freq # 自由空间波长 + + # 计算有效介电常数 + er_eff = (er + 1) / 2 + (er - 1) / 2 * np.sqrt(1 + 12 * thickness / width) + + # 计算贴片长度扩展 + delta_l = (thickness / 0.001) * 0.412 * (er_eff + 0.3) * (width / thickness + 0.264) / ((er_eff - 0.258) * (width / thickness + 0.8)) + delta_l_m = delta_l * 0.001 # 转换为米 + + # 计算实际贴片长度 + lambda_g = lambda0 / np.sqrt(er_eff) + patch_length = lambda_g / 2 - 2 * delta_l_m + + # 计算天线增益 (dBi) + gain = 10 * np.log10((60 * er_eff * (length / lambda0)**2 * width / lambda0) / ((er_eff + 1) * (1 + 0.25 * thickness / lambda_g))) + gain_dbi = gain + 2.15 # 转换为dBi + + # 计算波束宽度 + beamwidth_h = 101 * lambda0 / width # 水平波束宽度 (度) + beamwidth_e = 101 * lambda0 / length # 垂直波束宽度 (度) + + # 计算有效面积 + effective_area = (lambda0**2 * 10**(gain_dbi/10)) / (4 * np.pi) + + # 计算输入阻抗 + input_impedance = 377 * (width / length) * np.sqrt(er) # Ω + + return { + '天线增益': gain_dbi, + '波束宽度H': beamwidth_h, + '波束宽度E': beamwidth_e, + '有效面积': effective_area, + '输入阻抗': input_impedance, + '有效介电常数': er_eff, + '贴片长度': patch_length, + '天线效率': efficiency + } + + def calculate_horn_antenna(self, freq, length, width, efficiency): + # 计算喇叭天线参数 + c0 = 299792458 # 真空中的光速 + lambda0 = c0 / freq # 自由空间波长 + + # 计算天线增益 (dBi) + gain = 10 * np.log10((4 * np.pi * length * width) / lambda0**2) + gain_dbi = gain * efficiency + + # 计算波束宽度 + beamwidth_h = 51 * lambda0 / width # 水平波束宽度 (度) + beamwidth_e = 51 * lambda0 / length # 垂直波束宽度 (度) + + # 计算有效面积 + effective_area = (length * width) * efficiency + + # 计算输入阻抗 + input_impedance = 50 # Ω + + return { + '天线增益': gain_dbi, + '波束宽度H': beamwidth_h, + '波束宽度E': beamwidth_e, + '有效面积': effective_area, + '输入阻抗': input_impedance, + '天线效率': efficiency + } + + def calculate_yagi_antenna(self, freq, length, width, efficiency): + # 计算八木天线参数 + c0 = 299792458 # 真空中的光速 + lambda0 = c0 / freq # 自由空间波长 + + # 计算天线增益 (dBi) + # 假设八木天线有6个元素 + gain_dbi = 12.0 * efficiency + + # 计算波束宽度 + beamwidth_h = 45 # 水平波束宽度 (度) + beamwidth_e = 45 # 垂直波束宽度 (度) + + # 计算有效面积 + effective_area = (lambda0**2 * 10**(gain_dbi/10)) / (4 * np.pi) + + # 计算输入阻抗 + input_impedance = 50 # Ω + + return { + '天线增益': gain_dbi, + '波束宽度H': beamwidth_h, + '波束宽度E': beamwidth_e, + '有效面积': effective_area, + '输入阻抗': input_impedance, + '天线效率': efficiency + } + + def calculate_parabolic_antenna(self, freq, diameter, efficiency): + # 计算抛物面天线参数 + c0 = 299792458 # 真空中的光速 + lambda0 = c0 / freq # 自由空间波长 + + # 计算天线增益 (dBi) + gain = 10 * np.log10((np.pi * diameter / lambda0)**2 * efficiency) + gain_dbi = gain + + # 计算波束宽度 + beamwidth = 70 * lambda0 / diameter # 波束宽度 (度) + + # 计算有效面积 + effective_area = (np.pi * (diameter / 2)**2) * efficiency + + # 计算输入阻抗 + input_impedance = 50 # Ω + + return { + '天线增益': gain_dbi, + '波束宽度H': beamwidth, + '波束宽度E': beamwidth, + '有效面积': effective_area, + '输入阻抗': input_impedance, + '天线效率': efficiency + } + + def design_antenna(self): + # 天线设计优化 + try: + # 获取输入参数 + antenna_type = self.antenna_type_combo.currentText() + freq = self.freq_edit.value() * 1e9 # Hz + er = self.er_edit.value() + + # 根据天线类型进行设计优化 + if antenna_type == '偶极子天线': + design_results = self.design_dipole_antenna(freq) + elif antenna_type == '贴片天线': + design_results = self.design_patch_antenna(freq, er) + elif antenna_type == '喇叭天线': + design_results = self.design_horn_antenna(freq) + elif antenna_type == '八木天线': + design_results = self.design_yagi_antenna(freq) + elif antenna_type == '抛物面天线': + design_results = self.design_parabolic_antenna(freq) + else: + design_results = {} + + # 显示设计结果 + self.display_design_results(design_results) + + except Exception as e: + QMessageBox.warning(self, '警告', f'设计错误: {str(e)}') + + def design_dipole_antenna(self, freq): + # 设计偶极子天线 + c0 = 299792458 # 真空中的光速 + lambda0 = c0 / freq # 自由空间波长 + + # 计算半波偶极子长度 + length = lambda0 / 2 + + return { + '天线类型': '偶极子天线', + '设计频率': freq, + '半波偶极子长度': length, + '建议导线直径': length / 100, + '预期增益': 2.15, + '预期效率': 90.0 + } + + def design_patch_antenna(self, freq, er): + # 设计贴片天线 + c0 = 299792458 # 真空中的光速 + lambda0 = c0 / freq # 自由空间波长 + + # 计算贴片宽度 + width = lambda0 / (2 * np.sqrt((er + 1) / 2)) + + # 计算有效介电常数 + er_eff = (er + 1) / 2 + (er - 1) / 2 * np.sqrt(1 + 12 * 0.0016 / width) # 假设介质厚度为1.6mm + + # 计算贴片长度 + lambda_g = lambda0 / np.sqrt(er_eff) + length = lambda_g / 2 + + return { + '天线类型': '贴片天线', + '设计频率': freq, + '贴片宽度': width, + '贴片长度': length, + '相对介电常数': er, + '建议介质厚度': 0.0016, + '预期增益': 6.5, + '预期效率': 85.0 + } + + def design_horn_antenna(self, freq): + # 设计喇叭天线 + c0 = 299792458 # 真空中的光速 + lambda0 = c0 / freq # 自由空间波长 + + # 计算喇叭尺寸 + width = 3 * lambda0 + length = 5 * lambda0 + + return { + '天线类型': '喇叭天线', + '设计频率': freq, + '喇叭宽度': width, + '喇叭长度': length, + '预期增益': 15.0, + '预期效率': 95.0 + } + + def design_yagi_antenna(self, freq): + # 设计八木天线 + c0 = 299792458 # 真空中的光速 + lambda0 = c0 / freq # 自由空间波长 + + # 计算八木天线尺寸 + director_length = 0.47 * lambda0 + reflector_length = 0.52 * lambda0 + driven_element_length = 0.49 * lambda0 + spacing = 0.2 * lambda0 + + return { + '天线类型': '八木天线', + '设计频率': freq, + '引向器长度': director_length, + '反射器长度': reflector_length, + '辐射器长度': driven_element_length, + '元素间距': spacing, + '建议元素数量': 6, + '预期增益': 12.0, + '预期效率': 80.0 + } + + def design_parabolic_antenna(self, freq): + # 设计抛物面天线 + c0 = 299792458 # 真空中的光速 + lambda0 = c0 / freq # 自由空间波长 + + # 计算抛物面直径(假设增益为30dBi) + gain = 30.0 + efficiency = 0.7 + diameter = lambda0 * np.sqrt(4 * np.pi * 10**(gain/10) / (efficiency * 4 * np.pi)) + + return { + '天线类型': '抛物面天线', + '设计频率': freq, + '抛物面直径': diameter, + '预期增益': gain, + '预期效率': efficiency * 100 + } + + def display_results(self, params): + # 显示计算结果到表格 + self.result_table.setRowCount(0) + self.calculation_results.append(params) + + # 添加结果到表格 + self.add_result_row('天线类型', params['天线类型'], '') + self.add_result_row('频率', params['频率']/1e9, 'GHz') + self.add_result_row('自由空间波长', params['自由空间波长']*1e3, 'mm') + self.add_result_row('天线长度', params['天线长度']*1e3, 'mm') + self.add_result_row('天线宽度', params['天线宽度']*1e3, 'mm') + self.add_result_row('相对介电常数', params['相对介电常数'], '') + self.add_result_row('介质厚度', params['介质厚度']*1e3, 'mm') + self.add_result_row('天线效率', params['天线效率']*100, '%') + self.add_result_row('天线增益', params['天线增益'], 'dBi') + self.add_result_row('水平波束宽度', params['波束宽度H'], '°') + self.add_result_row('垂直波束宽度', params['波束宽度E'], '°') + self.add_result_row('有效面积', params['有效面积']*1e6, 'cm²') + self.add_result_row('输入阻抗', f"{params['输入阻抗'].real:.2f}+j{params['输入阻抗'].imag:.2f}" if hasattr(params['输入阻抗'], 'real') else params['输入阻抗'], 'Ω') + + # 添加特定天线类型的参数 + if '有效介电常数' in params: + self.add_result_row('有效介电常数', params['有效介电常数'], '') + if '贴片长度' in params: + self.add_result_row('贴片长度', params['贴片长度']*1e3, 'mm') + + # 更新增益显示 + self.gain_edit.setValue(params['天线增益']) + + # 显示结果详情 + detail_text = "天线参数计算结果:\n\n" + detail_text += f"天线类型: {params['天线类型']}\n" + detail_text += f"频率: {params['频率']/1e9:.2f} GHz\n" + detail_text += f"自由空间波长: {params['自由空间波长']*1e3:.2f} mm\n" + detail_text += f"天线长度: {params['天线长度']*1e3:.2f} mm\n" + detail_text += f"天线宽度: {params['天线宽度']*1e3:.2f} mm\n" + detail_text += f"相对介电常数: {params['相对介电常数']:.2f}\n" + detail_text += f"介质厚度: {params['介质厚度']*1e3:.2f} mm\n" + detail_text += f"天线效率: {params['天线效率']*100:.2f}%\n" + detail_text += f"天线增益: {params['天线增益']:.2f} dBi\n" + detail_text += f"水平波束宽度: {params['波束宽度H']:.2f}°\n" + detail_text += f"垂直波束宽度: {params['波束宽度E']:.2f}°\n" + detail_text += f"有效面积: {params['有效面积']*1e6:.2f} cm²\n" + detail_text += f"输入阻抗: {params['输入阻抗']:.2f} Ω\n" + + self.result_detail.setText(detail_text) + + def display_design_results(self, design_results): + # 显示天线设计结果 + if not design_results: + return + + result_text = "天线设计优化结果:\n\n" + result_text += f"天线类型: {design_results['天线类型']}\n" + result_text += f"设计频率: {design_results['设计频率']/1e9:.2f} GHz\n" + + if '半波偶极子长度' in design_results: + result_text += f"半波偶极子长度: {design_results['半波偶极子长度']*1e3:.2f} mm\n" + result_text += f"建议导线直径: {design_results['建议导线直径']*1e3:.2f} mm\n" + + if '贴片宽度' in design_results: + result_text += f"贴片宽度: {design_results['贴片宽度']*1e3:.2f} mm\n" + result_text += f"贴片长度: {design_results['贴片长度']*1e3:.2f} mm\n" + result_text += f"相对介电常数: {design_results['相对介电常数']:.2f}\n" + result_text += f"建议介质厚度: {design_results['建议介质厚度']*1e3:.2f} mm\n" + + if '喇叭宽度' in design_results: + result_text += f"喇叭宽度: {design_results['喇叭宽度']*1e3:.2f} mm\n" + result_text += f"喇叭长度: {design_results['喇叭长度']*1e3:.2f} mm\n" + + if '引向器长度' in design_results: + result_text += f"引向器长度: {design_results['引向器长度']*1e3:.2f} mm\n" + result_text += f"反射器长度: {design_results['反射器长度']*1e3:.2f} mm\n" + result_text += f"辐射器长度: {design_results['辐射器长度']*1e3:.2f} mm\n" + result_text += f"元素间距: {design_results['元素间距']*1e3:.2f} mm\n" + result_text += f"建议元素数量: {design_results['建议元素数量']}\n" + + if '抛物面直径' in design_results: + result_text += f"抛物面直径: {design_results['抛物面直径']*1e3:.2f} mm\n" + + result_text += f"预期增益: {design_results['预期增益']:.2f} dBi\n" + result_text += f"预期效率: {design_results['预期效率']:.2f}%\n" + + self.result_detail.setText(result_text) + + def add_result_row(self, name, value, unit): + # 添加结果行到表格 + row = self.result_table.rowCount() + self.result_table.insertRow(row) + self.result_table.setItem(row, 0, QTableWidgetItem(name)) + self.result_table.setItem(row, 1, QTableWidgetItem(str(value))) + self.result_table.setItem(row, 2, QTableWidgetItem(unit)) + + def plot_2d_radiation_pattern(self): + # 绘制2D辐射方向图 + if not self.calculation_results: + QMessageBox.warning(self, '警告', '没有计算结果可以绘制') + return + + # 获取最新的计算结果 + params = self.calculation_results[-1] + + # 生成角度数据 + theta = np.linspace(0, 2 * np.pi, 360) + + # 生成辐射方向图数据 + if params['天线类型'] == '偶极子天线': + # 偶极子天线辐射方向图 + pattern = np.sin(theta)**2 + elif params['天线类型'] == '贴片天线': + # 贴片天线辐射方向图 + pattern = (np.sin(theta))**2 * (np.cos(np.pi * np.sin(theta) / 2))**2 + elif params['天线类型'] == '喇叭天线': + # 喇叭天线辐射方向图 + pattern = (np.sin(theta))**2 * (np.cos(np.pi * np.sin(theta) / 2))**2 + elif params['天线类型'] == '八木天线': + # 八木天线辐射方向图 + pattern = (np.sin(theta))**4 + elif params['天线类型'] == '抛物面天线': + # 抛物面天线辐射方向图 + pattern = (np.sin(theta))**10 + else: + # 默认辐射方向图 + pattern = np.sin(theta)**2 + + # 绘制极坐标图 + self.figure.clear() + ax = self.figure.add_subplot(111, polar=True) + ax.plot(theta, pattern) + ax.set_title(f'{params["天线类型"]} 2D辐射方向图') + ax.set_theta_zero_location('N') + ax.set_theta_direction(-1) + self.canvas.draw() + + def plot_3d_radiation_pattern(self): + # 绘制3D辐射方向图 + if not self.calculation_results: + QMessageBox.warning(self, '警告', '没有计算结果可以绘制') + return + + # 获取最新的计算结果 + params = self.calculation_results[-1] + + # 生成角度数据 + theta = np.linspace(0, 2 * np.pi, 180) + phi = np.linspace(0, np.pi, 90) + theta, phi = np.meshgrid(theta, phi) + + # 生成辐射方向图数据 + if params['天线类型'] == '偶极子天线': + # 偶极子天线辐射方向图 + pattern = np.sin(phi)**2 + elif params['天线类型'] == '贴片天线': + # 贴片天线辐射方向图 + pattern = (np.sin(phi))**2 * (np.cos(np.pi * np.sin(phi) / 2))**2 + elif params['天线类型'] == '喇叭天线': + # 喇叭天线辐射方向图 + pattern = (np.sin(phi))**2 * (np.cos(np.pi * np.sin(phi) / 2))**2 + elif params['天线类型'] == '八木天线': + # 八木天线辐射方向图 + pattern = (np.sin(phi))**4 + elif params['天线类型'] == '抛物面天线': + # 抛物面天线辐射方向图 + pattern = (np.sin(phi))**10 + else: + # 默认辐射方向图 + pattern = np.sin(phi)**2 + + # 转换为笛卡尔坐标 + x = pattern * np.sin(phi) * np.cos(theta) + y = pattern * np.sin(phi) * np.sin(theta) + z = pattern * np.cos(phi) + + # 绘制3D图 + self.figure.clear() + ax = self.figure.add_subplot(111, projection='3d') + ax.plot_surface(x, y, z, cmap='viridis') + ax.set_title(f'{params["天线类型"]} 3D辐射方向图') + self.canvas.draw() + + def clear_chart(self): + # 清空图表 + self.figure.clear() + self.canvas.draw() + + def clear(self): + # 清空输入和结果 + self.antenna_type_combo.setCurrentText('偶极子天线') + self.freq_edit.setValue(2.4) + self.length_edit.setValue(62.5) + self.length_unit_combo.setCurrentText('mm') + self.width_edit.setValue(31.25) + self.width_unit_combo.setCurrentText('mm') + self.er_edit.setValue(4.4) + self.thickness_edit.setValue(1.6) + self.thickness_unit_combo.setCurrentText('mm') + self.efficiency_edit.setValue(90.0) + self.result_table.setRowCount(0) + self.result_detail.clear() + self.clear_chart() \ No newline at end of file diff --git a/tools/filter_designer.py b/tools/filter_designer.py new file mode 100644 index 0000000..75f43c8 --- /dev/null +++ b/tools/filter_designer.py @@ -0,0 +1,516 @@ +import sys +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QComboBox, QTableWidget, QTableWidgetItem, QTextEdit, QGroupBox, QGridLayout, QDoubleSpinBox, QMessageBox, QSplitter, QSpinBox, QTabWidget +from PyQt5.QtGui import QFont, QIcon +from PyQt5.QtCore import Qt +from data.history_manager import HistoryManager +from scipy.signal import butter, cheby1, cheby2, ellip, bessel, freqs, freqz + +class FilterDesigner(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + self.history_manager = HistoryManager() + self.filter_results = [] + + def init_ui(self): + # 创建主布局 + main_layout = QVBoxLayout(self) + + # 创建标题 + title_label = QLabel('滤波器设计工具') + title_label.setFont(QFont('Consolas', 16, QFont.Bold)) + main_layout.addWidget(title_label) + + # 创建标签页 + tab_widget = QTabWidget() + + # 创建设计标签 + design_tab = QWidget() + design_layout = QVBoxLayout(design_tab) + + # 创建输入区域 + input_group = QGroupBox('滤波器参数') + input_layout = QGridLayout(input_group) + + # 滤波器类型 + filter_type_label = QLabel('滤波器类型:') + self.filter_type_combo = QComboBox() + self.filter_type_combo.addItems(['低通', '高通', '带通', '带阻']) + self.filter_type_combo.currentTextChanged.connect(self.update_filter_params) + input_layout.addWidget(filter_type_label, 0, 0) + input_layout.addWidget(self.filter_type_combo, 0, 1) + + # 滤波器响应 + response_label = QLabel('滤波器响应:') + self.response_combo = QComboBox() + self.response_combo.addItems(['巴特沃斯', '切比雪夫I型', '切比雪夫II型', '椭圆', '贝塞尔']) + input_layout.addWidget(response_label, 0, 2) + input_layout.addWidget(self.response_combo, 0, 3) + + # 滤波器阶数 + order_label = QLabel('滤波器阶数:') + self.order_spin = QSpinBox() + self.order_spin.setRange(1, 20) + self.order_spin.setValue(4) + input_layout.addWidget(order_label, 1, 0) + input_layout.addWidget(self.order_spin, 1, 1) + + # 截止频率1 + cutoff1_label = QLabel('截止频率1 (GHz):') + self.cutoff1_edit = QDoubleSpinBox() + self.cutoff1_edit.setRange(0.1, 100.0) + self.cutoff1_edit.setValue(1.0) + input_layout.addWidget(cutoff1_label, 1, 2) + input_layout.addWidget(self.cutoff1_edit, 1, 3) + + # 截止频率2 + cutoff2_label = QLabel('截止频率2 (GHz):') + self.cutoff2_edit = QDoubleSpinBox() + self.cutoff2_edit.setRange(0.1, 100.0) + self.cutoff2_edit.setValue(2.0) + self.cutoff2_edit.setEnabled(False) + input_layout.addWidget(cutoff2_label, 2, 2) + input_layout.addWidget(self.cutoff2_edit, 2, 3) + + # 通带波纹 + ripple_label = QLabel('通带波纹 (dB):') + self.ripple_edit = QDoubleSpinBox() + self.ripple_edit.setRange(0.1, 10.0) + self.ripple_edit.setValue(1.0) + input_layout.addWidget(ripple_label, 2, 0) + input_layout.addWidget(self.ripple_edit, 2, 1) + + # 阻带衰减 + attenuation_label = QLabel('阻带衰减 (dB):') + self.attenuation_edit = QDoubleSpinBox() + self.attenuation_edit.setRange(10.0, 100.0) + self.attenuation_edit.setValue(40.0) + input_layout.addWidget(attenuation_label, 3, 0) + input_layout.addWidget(self.attenuation_edit, 3, 1) + + # 特征阻抗 + z0_label = QLabel('特征阻抗 (Ω):') + self.z0_edit = QDoubleSpinBox() + self.z0_edit.setRange(50.0, 75.0) + self.z0_edit.setValue(50.0) + input_layout.addWidget(z0_label, 3, 2) + input_layout.addWidget(self.z0_edit, 3, 3) + + design_layout.addWidget(input_group) + + # 创建计算按钮区域 + button_layout = QHBoxLayout() + + # 设计滤波器按钮 + design_button = QPushButton('设计滤波器') + design_button.setIcon(QIcon('resources/icons/design.png')) + design_button.clicked.connect(self.design_filter) + button_layout.addWidget(design_button) + + # 优化滤波器按钮 + optimize_button = QPushButton('优化设计') + optimize_button.setIcon(QIcon('resources/icons/optimize.png')) + optimize_button.clicked.connect(self.optimize_filter) + button_layout.addWidget(optimize_button) + + # 清除按钮 + clear_button = QPushButton('清除') + clear_button.setIcon(QIcon('resources/icons/clear.png')) + clear_button.clicked.connect(self.clear) + button_layout.addWidget(clear_button) + + design_layout.addLayout(button_layout) + + # 创建结果显示区域 + result_group = QGroupBox('设计结果') + result_layout = QVBoxLayout(result_group) + + # 创建结果表格 + self.result_table = QTableWidget() + self.result_table.setColumnCount(4) + self.result_table.setHorizontalHeaderLabels(['元件类型', '元件值', '单位', '位置']) + self.result_table.horizontalHeader().setStretchLastSection(True) + result_layout.addWidget(self.result_table) + + # 创建结果详情区域 + self.result_detail = QTextEdit() + self.result_detail.setReadOnly(True) + self.result_detail.setFont(QFont('Consolas', 10)) + result_layout.addWidget(self.result_detail) + + design_layout.addWidget(result_group) + + tab_widget.addTab(design_tab, '滤波器设计') + + # 创建分析标签 + analysis_tab = QWidget() + analysis_layout = QVBoxLayout(analysis_tab) + + # 创建可视化区域 + visual_group = QGroupBox('频率响应') + visual_layout = QVBoxLayout(visual_group) + + # 创建图表画布 + self.figure = plt.figure(figsize=(8, 6)) + self.canvas = FigureCanvas(self.figure) + visual_layout.addWidget(self.canvas) + + # 图表控制按钮 + chart_button_layout = QHBoxLayout() + + # 绘制幅频响应按钮 + plot_mag_button = QPushButton('绘制幅频响应') + plot_mag_button.clicked.connect(self.plot_magnitude_response) + chart_button_layout.addWidget(plot_mag_button) + + # 绘制相频响应按钮 + plot_phase_button = QPushButton('绘制相频响应') + plot_phase_button.clicked.connect(self.plot_phase_response) + chart_button_layout.addWidget(plot_phase_button) + + # 绘制群延迟按钮 + plot_group_button = QPushButton('绘制群延迟') + plot_group_button.clicked.connect(self.plot_group_delay) + chart_button_layout.addWidget(plot_group_button) + + # 清空图表按钮 + clear_chart_button = QPushButton('清空图表') + clear_chart_button.clicked.connect(self.clear_chart) + chart_button_layout.addWidget(clear_chart_button) + + visual_layout.addLayout(chart_button_layout) + + analysis_layout.addWidget(visual_group) + + tab_widget.addTab(analysis_tab, '频率响应分析') + + main_layout.addWidget(tab_widget) + + # 填充剩余空间 + main_layout.addStretch() + + def update_filter_params(self, filter_type): + # 根据滤波器类型更新参数 + if filter_type in ['带通', '带阻']: + self.cutoff2_edit.setEnabled(True) + else: + self.cutoff2_edit.setEnabled(False) + + def design_filter(self): + # 设计滤波器 + try: + # 获取输入参数 + filter_type = self.filter_type_combo.currentText() + response = self.response_combo.currentText() + order = self.order_spin.value() + cutoff1 = self.cutoff1_edit.value() * 1e9 # Hz + cutoff2 = self.cutoff2_edit.value() * 1e9 # Hz + ripple = self.ripple_edit.value() # dB + attenuation = self.attenuation_edit.value() # dB + z0 = self.z0_edit.value() # Ω + + # 确定滤波器类型 + btype = self.get_filter_type(filter_type) + + # 确定截止频率 + if filter_type in ['低通', '高通']: + Wn = cutoff1 / (2 * np.pi) # rad/s + else: + Wn = [cutoff1 / (2 * np.pi), cutoff2 / (2 * np.pi)] # rad/s + + # 设计滤波器 + if response == '巴特沃斯': + b, a = butter(order, Wn, btype=btype, analog=True) + elif response == '切比雪夫I型': + b, a = cheby1(order, ripple, Wn, btype=btype, analog=True) + elif response == '切比雪夫II型': + b, a = cheby2(order, attenuation, Wn, btype=btype, analog=True) + elif response == '椭圆': + b, a = ellip(order, ripple, attenuation, Wn, btype=btype, analog=True) + elif response == '贝塞尔': + b, a = bessel(order, Wn, btype=btype, analog=True, norm='delay') + else: + b, a = butter(order, Wn, btype=btype, analog=True) + + # 计算元件值 + elements = self.calculate_filter_elements(b, a, z0, filter_type) + + # 保存结果 + result = { + 'filter_type': filter_type, + 'response': response, + 'order': order, + 'cutoff1': cutoff1, + 'cutoff2': cutoff2, + 'ripple': ripple, + 'attenuation': attenuation, + 'z0': z0, + 'b': b, + 'a': a, + 'elements': elements + } + + self.filter_results.append(result) + + # 显示结果 + self.display_results(result) + + # 保存到历史 + self.history_manager.add_history( + tool_name='滤波器设计工具', + input_data=f"类型: {filter_type}, 响应: {response}, 阶数: {order}, 截止频率: {cutoff1/1e9} GHz", + output_data=f"滤波器设计完成,包含 {len(elements)} 个元件", + calculation_type='filter_design' + ) + + except Exception as e: + QMessageBox.warning(self, '警告', f'设计错误: {str(e)}') + + def get_filter_type(self, filter_type): + # 转换滤波器类型为scipy.signal所需的格式 + if filter_type == '低通': + return 'lowpass' + elif filter_type == '高通': + return 'highpass' + elif filter_type == '带通': + return 'bandpass' + elif filter_type == '带阻': + return 'bandstop' + else: + return 'lowpass' + + def calculate_filter_elements(self, b, a, z0, filter_type): + # 计算滤波器元件值 + elements = [] + + # 归一化传输函数 + b_norm = b / b[0] + a_norm = a / a[0] + + # 实现巴特沃斯滤波器的元件计算 + if self.response_combo.currentText() == '巴特沃斯': + # 使用巴特沃斯滤波器的设计公式 + for i in range(1, len(b_norm)): + if i % 2 == 1: + # 串联部分 + if i == 1: + # 第一个元件 + r = z0 + elements.append({'type': 'R', 'value': r, 'unit': 'Ω', 'position': '输入'}) + + # 计算电感和电容 + if filter_type in ['低通', '带通']: + l = z0 * b_norm[i] + c = 1 / (z0 * a_norm[i]) + + elements.append({'type': 'L', 'value': l, 'unit': 'H', 'position': f'串联{i}'}) + elements.append({'type': 'C', 'value': c, 'unit': 'F', 'position': f'并联{i}'}) + elif filter_type in ['高通', '带阻']: + c = 1 / (z0 * b_norm[i]) + l = z0 / a_norm[i] + + elements.append({'type': 'C', 'value': c, 'unit': 'F', 'position': f'串联{i}'}) + elements.append({'type': 'L', 'value': l, 'unit': 'H', 'position': f'并联{i}'}) + + # 转换单位为更实用的单位 + for element in elements: + if element['type'] == 'L': + if element['value'] < 1e-6: + element['value'] *= 1e9 + element['unit'] = 'nH' + else: + element['value'] *= 1e6 + element['unit'] = 'μH' + elif element['type'] == 'C': + if element['value'] < 1e-9: + element['value'] *= 1e12 + element['unit'] = 'pF' + else: + element['value'] *= 1e9 + element['unit'] = 'nF' + + return elements + + def optimize_filter(self): + # 优化滤波器设计 + try: + # 获取当前设计结果 + if not self.filter_results: + QMessageBox.warning(self, '警告', '没有设计结果可以优化') + return + + current_result = self.filter_results[-1] + + # 简单的优化算法:调整阶数以满足阻带衰减要求 + order = current_result['order'] + attenuation = current_result['attenuation'] + + # 计算当前阻带衰减 + w, h = freqs(current_result['b'], current_result['a'], worN=np.logspace(0, 12, 1000)) + mag = 20 * np.log10(np.abs(h)) + + # 找到阻带中的最小衰减 + if current_result['filter_type'] == '低通': + stopband_mag = mag[w > 2 * current_result['cutoff1']] + elif current_result['filter_type'] == '高通': + stopband_mag = mag[w < 0.5 * current_result['cutoff1']] + elif current_result['filter_type'] == '带通': + stopband_mag = np.concatenate([ + mag[w < 0.5 * current_result['cutoff1']], + mag[w > 2 * current_result['cutoff2']] + ]) + elif current_result['filter_type'] == '带阻': + stopband_mag = mag[ + (w > 0.5 * current_result['cutoff1']) & + (w < 2 * current_result['cutoff2']) + ] + else: + stopband_mag = mag + + current_attenuation = np.min(stopband_mag) if len(stopband_mag) > 0 else 0 + + # 如果当前衰减不满足要求,增加阶数 + if current_attenuation > -attenuation: + new_order = order + 1 + self.order_spin.setValue(new_order) + + # 重新设计滤波器 + self.design_filter() + + QMessageBox.information(self, '优化完成', f'滤波器阶数已从 {order} 增加到 {new_order},以满足阻带衰减要求') + else: + QMessageBox.information(self, '优化完成', '当前设计已经满足要求,无需优化') + + except Exception as e: + QMessageBox.warning(self, '警告', f'优化错误: {str(e)}') + + def display_results(self, result): + # 显示设计结果 + self.result_table.setRowCount(0) + + # 显示滤波器参数 + detail_text = "滤波器设计结果:\n\n" + detail_text += f"滤波器类型: {result['filter_type']}\n" + detail_text += f"滤波器响应: {result['response']}\n" + detail_text += f"滤波器阶数: {result['order']}\n" + detail_text += f"截止频率1: {result['cutoff1']/1e9:.2f} GHz\n" + if result['filter_type'] in ['带通', '带阻']: + detail_text += f"截止频率2: {result['cutoff2']/1e9:.2f} GHz\n" + detail_text += f"通带波纹: {result['ripple']:.2f} dB\n" + detail_text += f"阻带衰减: {result['attenuation']:.2f} dB\n" + detail_text += f"特征阻抗: {result['z0']:.2f} Ω\n" + detail_text += f"元件数量: {len(result['elements'])}\n\n" + detail_text += "元件列表:\n" + + # 显示元件值 + for i, element in enumerate(result['elements']): + self.add_result_row(element['type'], element['value'], element['unit'], element['position']) + detail_text += f"{element['type']}{i+1}: {element['value']:.2f} {element['unit']} ({element['position']})\n" + + self.result_detail.setText(detail_text) + + def add_result_row(self, type_, value, unit, position): + # 添加结果行到表格 + row = self.result_table.rowCount() + self.result_table.insertRow(row) + self.result_table.setItem(row, 0, QTableWidgetItem(type_)) + self.result_table.setItem(row, 1, QTableWidgetItem(f"{value:.2f}")) + self.result_table.setItem(row, 2, QTableWidgetItem(unit)) + self.result_table.setItem(row, 3, QTableWidgetItem(position)) + + def plot_magnitude_response(self): + # 绘制幅频响应 + if not self.filter_results: + QMessageBox.warning(self, '警告', '没有设计结果可以绘制') + return + + # 获取最新的设计结果 + result = self.filter_results[-1] + + # 计算频率响应 + w, h = freqs(result['b'], result['a'], worN=np.logspace(0, 12, 1000)) + + # 绘制幅频响应 + self.figure.clear() + ax = self.figure.add_subplot(111) + ax.semilogx(w / (2 * np.pi), 20 * np.log10(np.abs(h))) + ax.set_title(f'{result["filter_type"]} {result["response"]} 滤波器幅频响应') + ax.set_xlabel('频率 (Hz)') + ax.set_ylabel('增益 (dB)') + ax.grid(True, which='both') + ax.axhline(-result['attenuation'], color='red', linestyle='--', label=f'阻带衰减: {result["attenuation"]} dB') + ax.axvline(result['cutoff1'], color='green', linestyle='--', label=f'截止频率1: {result["cutoff1"]/1e9:.2f} GHz') + if result['filter_type'] in ['带通', '带阻']: + ax.axvline(result['cutoff2'], color='blue', linestyle='--', label=f'截止频率2: {result["cutoff2"]/1e9:.2f} GHz') + ax.legend() + self.canvas.draw() + + def plot_phase_response(self): + # 绘制相频响应 + if not self.filter_results: + QMessageBox.warning(self, '警告', '没有设计结果可以绘制') + return + + # 获取最新的设计结果 + result = self.filter_results[-1] + + # 计算频率响应 + w, h = freqs(result['b'], result['a'], worN=np.logspace(0, 12, 1000)) + + # 绘制相频响应 + self.figure.clear() + ax = self.figure.add_subplot(111) + ax.semilogx(w / (2 * np.pi), np.angle(h, deg=True)) + ax.set_title(f'{result["filter_type"]} {result["response"]} 滤波器相频响应') + ax.set_xlabel('频率 (Hz)') + ax.set_ylabel('相位 (度)') + ax.grid(True, which='both') + self.canvas.draw() + + def plot_group_delay(self): + # 绘制群延迟 + if not self.filter_results: + QMessageBox.warning(self, '警告', '没有设计结果可以绘制') + return + + # 获取最新的设计结果 + result = self.filter_results[-1] + + # 计算频率响应 + w, h = freqs(result['b'], result['a'], worN=np.logspace(0, 12, 1000)) + + # 计算群延迟 + group_delay = -np.diff(np.angle(h)) / np.diff(w) + w_group = (w[1:] + w[:-1]) / 2 + + # 绘制群延迟 + self.figure.clear() + ax = self.figure.add_subplot(111) + ax.semilogx(w_group / (2 * np.pi), group_delay) + ax.set_title(f'{result["filter_type"]} {result["response"]} 滤波器群延迟') + ax.set_xlabel('频率 (Hz)') + ax.set_ylabel('群延迟 (s)') + ax.grid(True, which='both') + self.canvas.draw() + + def clear_chart(self): + # 清空图表 + self.figure.clear() + self.canvas.draw() + + def clear(self): + # 清空输入和结果 + self.filter_type_combo.setCurrentText('低通') + self.response_combo.setCurrentText('巴特沃斯') + self.order_spin.setValue(4) + self.cutoff1_edit.setValue(1.0) + self.cutoff2_edit.setValue(2.0) + self.ripple_edit.setValue(1.0) + self.attenuation_edit.setValue(40.0) + self.z0_edit.setValue(50.0) + self.result_table.setRowCount(0) + self.result_detail.clear() + self.clear_chart() \ No newline at end of file diff --git a/tools/frequency_wavelength.py b/tools/frequency_wavelength.py new file mode 100644 index 0000000..3142f4a --- /dev/null +++ b/tools/frequency_wavelength.py @@ -0,0 +1,529 @@ +import sys +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QComboBox, QTableWidget, QTableWidgetItem, QTextEdit, QGroupBox, QGridLayout, QSpinBox, QDoubleSpinBox, QMessageBox +from PyQt5.QtGui import QFont, QIcon +from PyQt5.QtCore import Qt +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from utils.visualization import plot_wavelength_distribution +from data.history_manager import HistoryManager + +class FrequencyWavelengthCalculator(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + self.history_manager = HistoryManager() + self.calculation_results = [] + + def init_ui(self): + # 创建主布局 + main_layout = QVBoxLayout(self) + + # 创建标题 + title_label = QLabel('频率-波长计算器') + title_label.setFont(QFont('Consolas', 16, QFont.Bold)) + main_layout.addWidget(title_label) + + # 创建输入区域 + input_group = QGroupBox('输入参数') + input_layout = QGridLayout(input_group) + + # 频率输入 + freq_label = QLabel('频率:') + self.freq_edit = QLineEdit() + self.freq_edit.setPlaceholderText('输入频率值,支持逗号分隔批量输入') + freq_unit_label = QLabel('单位:') + self.freq_unit_combo = QComboBox() + self.freq_unit_combo.addItems(['Hz', 'kHz', 'MHz', 'GHz', 'THz']) + + input_layout.addWidget(freq_label, 0, 0) + input_layout.addWidget(self.freq_edit, 0, 1) + input_layout.addWidget(freq_unit_label, 0, 2) + input_layout.addWidget(self.freq_unit_combo, 0, 3) + + # 波长输入 + wave_label = QLabel('波长:') + self.wave_edit = QLineEdit() + self.wave_edit.setPlaceholderText('输入波长值,支持逗号分隔批量输入') + wave_unit_label = QLabel('单位:') + self.wave_unit_combo = QComboBox() + self.wave_unit_combo.addItems(['m', 'cm', 'mm', 'μm', 'nm']) + + input_layout.addWidget(wave_label, 1, 0) + input_layout.addWidget(self.wave_edit, 1, 1) + input_layout.addWidget(wave_unit_label, 1, 2) + input_layout.addWidget(self.wave_unit_combo, 1, 3) + + # 介质参数 + medium_label = QLabel('介质类型:') + self.medium_combo = QComboBox() + self.medium_combo.addItems(['真空', '空气', 'FR-4', '罗杰斯RO4003C', '氧化铝陶瓷']) + self.medium_combo.currentTextChanged.connect(self.update_medium_params) + + permittivity_label = QLabel('相对介电常数:') + self.permittivity_edit = QLineEdit() + self.permittivity_edit.setText('1.0006') + self.permittivity_edit.setReadOnly(True) + + permeability_label = QLabel('相对磁导率:') + self.permeability_edit = QLineEdit() + self.permeability_edit.setText('1.0') + self.permeability_edit.setReadOnly(True) + + input_layout.addWidget(medium_label, 2, 0) + input_layout.addWidget(self.medium_combo, 2, 1) + input_layout.addWidget(permittivity_label, 2, 2) + input_layout.addWidget(self.permittivity_edit, 2, 3) + + input_layout.addWidget(permeability_label, 3, 2) + input_layout.addWidget(self.permeability_edit, 3, 3) + + main_layout.addWidget(input_group) + + # 创建计算按钮区域 + button_layout = QHBoxLayout() + + # 计算按钮 + calc_button = QPushButton('计算') + calc_button.setIcon(QIcon('resources/icons/calculate.png')) + calc_button.clicked.connect(self.calculate) + button_layout.addWidget(calc_button) + + # 清除按钮 + clear_button = QPushButton('清除') + clear_button.setIcon(QIcon('resources/icons/clear.png')) + clear_button.clicked.connect(self.clear) + button_layout.addWidget(clear_button) + + # 批量计算按钮 + batch_button = QPushButton('批量计算') + batch_button.setIcon(QIcon('resources/icons/batch.png')) + batch_button.clicked.connect(self.batch_calculate) + button_layout.addWidget(batch_button) + + # 频段预设按钮 + preset_button = QPushButton('频段预设') + preset_button.setIcon(QIcon('resources/icons/preset.png')) + preset_button.clicked.connect(self.show_preset_bands) + button_layout.addWidget(preset_button) + + main_layout.addLayout(button_layout) + + # 创建结果显示区域 + result_group = QGroupBox('计算结果') + result_layout = QVBoxLayout(result_group) + + # 创建表格显示结果 + self.result_table = QTableWidget() + self.result_table.setColumnCount(4) + self.result_table.setHorizontalHeaderLabels(['频率', '波长', '介质', '计算时间']) + self.result_table.horizontalHeader().setStretchLastSection(True) + result_layout.addWidget(self.result_table) + + # 创建结果详情区域 + self.result_detail = QTextEdit() + self.result_detail.setReadOnly(True) + self.result_detail.setFont(QFont('Consolas', 10)) + result_layout.addWidget(self.result_detail) + + main_layout.addWidget(result_group) + + # 创建可视化区域 + visual_group = QGroupBox('可视化') + visual_layout = QVBoxLayout(visual_group) + + # 创建图表画布 + self.figure, self.ax = plt.subplots(figsize=(8, 4)) + self.canvas = FigureCanvas(self.figure) + visual_layout.addWidget(self.canvas) + + # 图表控制按钮 + chart_button_layout = QHBoxLayout() + + # 频率分布按钮 + freq_dist_button = QPushButton('频率分布') + freq_dist_button.clicked.connect(self.plot_frequency_distribution) + chart_button_layout.addWidget(freq_dist_button) + + # 波长分布按钮 + wave_dist_button = QPushButton('波长分布') + wave_dist_button.clicked.connect(self.plot_wavelength_distribution) + chart_button_layout.addWidget(wave_dist_button) + + # 清空图表按钮 + clear_chart_button = QPushButton('清空图表') + clear_chart_button.clicked.connect(self.clear_chart) + chart_button_layout.addWidget(clear_chart_button) + + visual_layout.addLayout(chart_button_layout) + + main_layout.addWidget(visual_group) + + # 填充剩余空间 + main_layout.addStretch() + + def update_medium_params(self, medium): + # 更新介质参数 + medium_params = { + '真空': {'permittivity': 1.0, 'permeability': 1.0}, + '空气': {'permittivity': 1.0006, 'permeability': 1.0}, + 'FR-4': {'permittivity': 4.4, 'permeability': 1.0}, + '罗杰斯RO4003C': {'permittivity': 3.38, 'permeability': 1.0}, + '氧化铝陶瓷': {'permittivity': 9.8, 'permeability': 1.0} + } + + if medium in medium_params: + self.permittivity_edit.setText(str(medium_params[medium]['permittivity'])) + self.permeability_edit.setText(str(medium_params[medium]['permeability'])) + + def calculate(self): + # 计算频率和波长 + freq_text = self.freq_edit.text().strip() + wave_text = self.wave_edit.text().strip() + + # 检查输入 + if not freq_text and not wave_text: + QMessageBox.warning(self, '警告', '请输入频率或波长值') + return + + # 获取介质参数 + permittivity = float(self.permittivity_edit.text()) + permeability = float(self.permeability_edit.text()) + + # 计算光速 + c0 = 299792458 # 真空中的光速 + c = c0 / np.sqrt(permittivity * permeability) + + results = [] + + # 从频率计算波长 + if freq_text: + try: + freqs = [float(f) for f in freq_text.split(',')] + freq_unit = self.freq_unit_combo.currentText() + + for freq in freqs: + # 转换为Hz + if freq_unit == 'kHz': + freq_hz = freq * 1e3 + elif freq_unit == 'MHz': + freq_hz = freq * 1e6 + elif freq_unit == 'GHz': + freq_hz = freq * 1e9 + elif freq_unit == 'THz': + freq_hz = freq * 1e12 + else: + freq_hz = freq + + # 计算波长 + wavelength = c / freq_hz + + # 转换为合适的单位 + wave_unit = self.wave_unit_combo.currentText() + if wave_unit == 'cm': + wavelength_unit = wavelength * 100 + elif wave_unit == 'mm': + wavelength_unit = wavelength * 1000 + elif wave_unit == 'μm': + wavelength_unit = wavelength * 1e6 + elif wave_unit == 'nm': + wavelength_unit = wavelength * 1e9 + else: + wavelength_unit = wavelength + + results.append({ + 'type': 'frequency', + 'input': freq, + 'input_unit': freq_unit, + 'output': wavelength_unit, + 'output_unit': wave_unit, + 'medium': self.medium_combo.currentText() + }) + except ValueError: + QMessageBox.warning(self, '警告', '频率输入格式错误') + return + + # 从波长计算频率 + if wave_text: + try: + waves = [float(w) for w in wave_text.split(',')] + wave_unit = self.wave_unit_combo.currentText() + + for wave in waves: + # 转换为m + if wave_unit == 'cm': + wave_m = wave / 100 + elif wave_unit == 'mm': + wave_m = wave / 1000 + elif wave_unit == 'μm': + wave_m = wave / 1e6 + elif wave_unit == 'nm': + wave_m = wave / 1e9 + else: + wave_m = wave + + # 计算频率 + frequency = c / wave_m + + # 转换为合适的单位 + freq_unit = self.freq_unit_combo.currentText() + if freq_unit == 'kHz': + frequency_unit = frequency / 1e3 + elif freq_unit == 'MHz': + frequency_unit = frequency / 1e6 + elif freq_unit == 'GHz': + frequency_unit = frequency / 1e9 + elif freq_unit == 'THz': + frequency_unit = frequency / 1e12 + else: + frequency_unit = frequency + + results.append({ + 'type': 'wavelength', + 'input': wave, + 'input_unit': wave_unit, + 'output': frequency_unit, + 'output_unit': freq_unit, + 'medium': self.medium_combo.currentText() + }) + except ValueError: + QMessageBox.warning(self, '警告', '波长输入格式错误') + return + + # 显示结果 + self.display_results(results) + + # 保存到历史 + for result in results: + self.history_manager.add_history( + tool_name='频率-波长计算器', + input_data=f"{result['input']} {result['input_unit']}", + output_data=f"{result['output']:.6e} {result['output_unit']}", + medium=result['medium'], + calculation_type=result['type'] + ) + + def batch_calculate(self): + # 批量计算 + pass + + def show_preset_bands(self): + # 显示频段预设 + preset_dialog = PresetBandsDialog(self) + if preset_dialog.exec_() == preset_dialog.Accepted: + selected_bands = preset_dialog.get_selected_bands() + if selected_bands: + # 将选中的频段添加到输入框 + freq_text = ','.join([str(band['frequency']) for band in selected_bands]) + self.freq_edit.setText(freq_text) + self.freq_unit_combo.setCurrentText('GHz') + + def display_results(self, results): + # 显示结果到表格 + self.result_table.setRowCount(len(results)) + self.calculation_results = results + + for i, result in enumerate(results): + if result['type'] == 'frequency': + freq_str = f"{result['input']} {result['input_unit']}" + wave_str = f"{result['output']:.6e} {result['output_unit']}" + else: + wave_str = f"{result['input']} {result['input_unit']}" + freq_str = f"{result['output']:.6e} {result['output_unit']}" + + self.result_table.setItem(i, 0, QTableWidgetItem(freq_str)) + self.result_table.setItem(i, 1, QTableWidgetItem(wave_str)) + self.result_table.setItem(i, 2, QTableWidgetItem(result['medium'])) + self.result_table.setItem(i, 3, QTableWidgetItem('刚刚')) + + # 显示结果详情 + detail_text = "计算结果详情:\n\n" + for result in results: + if result['type'] == 'frequency': + detail_text += f"频率: {result['input']} {result['input_unit']} = {result['output']:.6e} {result['output_unit']} (波长)\n" + else: + detail_text += f"波长: {result['input']} {result['input_unit']} = {result['output']:.6e} {result['output_unit']} (频率)\n" + + detail_text += f"介质: {result['medium']}\n" + detail_text += f"介电常数: {self.permittivity_edit.text()}, 磁导率: {self.permeability_edit.text()}\n" + detail_text += "\n" + + self.result_detail.setText(detail_text) + + def plot_frequency_distribution(self): + # 绘制频率分布图表 + if not self.calculation_results: + QMessageBox.warning(self, '警告', '没有计算结果可以绘制') + return + + # 提取频率数据 + frequencies = [] + for result in self.calculation_results: + if result['type'] == 'frequency': + # 转换为Hz + freq = result['input'] + unit = result['input_unit'] + if unit == 'kHz': + freq *= 1e3 + elif unit == 'MHz': + freq *= 1e6 + elif unit == 'GHz': + freq *= 1e9 + elif unit == 'THz': + freq *= 1e12 + frequencies.append(freq) + else: + # 转换为Hz + freq = result['output'] + unit = result['output_unit'] + if unit == 'kHz': + freq *= 1e3 + elif unit == 'MHz': + freq *= 1e6 + elif unit == 'GHz': + freq *= 1e9 + elif unit == 'THz': + freq *= 1e12 + frequencies.append(freq) + + if frequencies: + plot_wavelength_distribution(self.figure, self.ax, frequencies, 'frequency') + self.canvas.draw() + + def plot_wavelength_distribution(self): + # 绘制波长分布图表 + if not self.calculation_results: + QMessageBox.warning(self, '警告', '没有计算结果可以绘制') + return + + # 提取波长数据 + wavelengths = [] + for result in self.calculation_results: + if result['type'] == 'frequency': + # 转换为m + wave = result['output'] + unit = result['output_unit'] + if unit == 'cm': + wave /= 100 + elif unit == 'mm': + wave /= 1000 + elif unit == 'μm': + wave /= 1e6 + elif unit == 'nm': + wave /= 1e9 + wavelengths.append(wave) + else: + # 转换为m + wave = result['input'] + unit = result['input_unit'] + if unit == 'cm': + wave /= 100 + elif unit == 'mm': + wave /= 1000 + elif unit == 'μm': + wave /= 1e6 + elif unit == 'nm': + wave /= 1e9 + wavelengths.append(wave) + + if wavelengths: + plot_wavelength_distribution(self.figure, self.ax, wavelengths, 'wavelength') + self.canvas.draw() + + def clear_chart(self): + # 清空图表 + self.ax.clear() + self.canvas.draw() + + def clear(self): + # 清空输入和结果 + self.freq_edit.clear() + self.wave_edit.clear() + self.result_table.setRowCount(0) + self.result_detail.clear() + self.clear_chart() + +class PresetBandsDialog(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle('频段预设') + self.setGeometry(200, 200, 400, 300) + + # 预设频段数据 + self.preset_bands = [ + {'name': 'GSM 900', 'frequency': 900, 'unit': 'MHz', 'description': '全球移动通信系统'}, + {'name': 'GSM 1800', 'frequency': 1800, 'unit': 'MHz', 'description': '全球移动通信系统'}, + {'name': 'UMTS', 'frequency': 2100, 'unit': 'MHz', 'description': '通用移动通信系统'}, + {'name': 'LTE 800', 'frequency': 800, 'unit': 'MHz', 'description': '长期演进技术'}, + {'name': 'LTE 1800', 'frequency': 1800, 'unit': 'MHz', 'description': '长期演进技术'}, + {'name': 'LTE 2600', 'frequency': 2600, 'unit': 'MHz', 'description': '长期演进技术'}, + {'name': 'WiFi 2.4GHz', 'frequency': 2.4, 'unit': 'GHz', 'description': '无线局域网'}, + {'name': 'WiFi 5GHz', 'frequency': 5, 'unit': 'GHz', 'description': '无线局域网'}, + {'name': 'Bluetooth', 'frequency': 2.4, 'unit': 'GHz', 'description': '蓝牙技术'}, + {'name': 'GPS', 'frequency': 1.575, 'unit': 'GHz', 'description': '全球定位系统'}, + {'name': '5G NR FR1', 'frequency': 3.5, 'unit': 'GHz', 'description': '第五代移动通信'}, + {'name': '5G NR FR2', 'frequency': 28, 'unit': 'GHz', 'description': '第五代移动通信'} + ] + + self.init_ui() + + def init_ui(self): + # 创建布局 + layout = QVBoxLayout(self) + + # 创建表格 + self.band_table = QTableWidget() + self.band_table.setColumnCount(3) + self.band_table.setHorizontalHeaderLabels(['频段名称', '频率', '描述']) + self.band_table.horizontalHeader().setStretchLastSection(True) + self.band_table.setRowCount(len(self.preset_bands)) + + for i, band in enumerate(self.preset_bands): + self.band_table.setItem(i, 0, QTableWidgetItem(band['name'])) + self.band_table.setItem(i, 1, QTableWidgetItem(f"{band['frequency']} {band['unit']}")) + self.band_table.setItem(i, 2, QTableWidgetItem(band['description'])) + + layout.addWidget(self.band_table) + + # 创建按钮区域 + button_layout = QHBoxLayout() + + # 确定按钮 + ok_button = QPushButton('确定') + ok_button.clicked.connect(self.accept) + button_layout.addWidget(ok_button) + + # 取消按钮 + cancel_button = QPushButton('取消') + cancel_button.clicked.connect(self.reject) + button_layout.addWidget(cancel_button) + + layout.addLayout(button_layout) + + def get_selected_bands(self): + # 获取选中的频段 + selected_bands = [] + selected_rows = self.band_table.selectedItems() + + # 去重行号 + rows = list(set(item.row() for item in selected_rows)) + + for row in rows: + selected_bands.append(self.preset_bands[row]) + + return selected_bands + + def accept(self): + # 确定按钮点击 + self.close() + return True + + def reject(self): + # 取消按钮点击 + self.close() + return False + + def exec_(self): + # 执行对话框 + self.show() + self.exec_() \ No newline at end of file diff --git a/tools/link_budget.py b/tools/link_budget.py new file mode 100644 index 0000000..f5a8ca1 --- /dev/null +++ b/tools/link_budget.py @@ -0,0 +1,775 @@ +import sys +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QComboBox, QTableWidget, QTableWidgetItem, QTextEdit, QGroupBox, QGridLayout, QDoubleSpinBox, QMessageBox, QSplitter, QSpinBox, QTabWidget, QFormLayout, QHeaderView +from PyQt5.QtGui import QFont, QIcon +from PyQt5.QtCore import Qt +from data.history_manager import HistoryManager + +class LinkBudgetAnalyzer(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + self.history_manager = HistoryManager() + self.link_results = [] + + def init_ui(self): + # 创建主布局 + main_layout = QVBoxLayout(self) + + # 创建标题 + title_label = QLabel('射频链路预算分析器') + title_label.setFont(QFont('Consolas', 16, QFont.Bold)) + main_layout.addWidget(title_label) + + # 创建标签页 + tab_widget = QTabWidget() + + # 创建设计标签 + design_tab = QWidget() + design_layout = QVBoxLayout(design_tab) + + # 创建系统参数区域 + system_group = QGroupBox('系统参数') + system_layout = QGridLayout(system_group) + + # 频率 + freq_label = QLabel('频率 (GHz):') + self.freq_edit = QDoubleSpinBox() + self.freq_edit.setRange(0.1, 100.0) + self.freq_edit.setValue(2.4) + system_layout.addWidget(freq_label, 0, 0) + system_layout.addWidget(self.freq_edit, 0, 1) + + # 距离 + distance_label = QLabel('距离 (km):') + self.distance_edit = QDoubleSpinBox() + self.distance_edit.setRange(0.1, 1000.0) + self.distance_edit.setValue(1.0) + system_layout.addWidget(distance_label, 0, 2) + system_layout.addWidget(self.distance_edit, 0, 3) + + # 传播环境 + env_label = QLabel('传播环境:') + self.env_combo = QComboBox() + self.env_combo.addItems(['自由空间', '市区', '郊区', '农村', '室内']) + system_layout.addWidget(env_label, 1, 0) + system_layout.addWidget(self.env_combo, 1, 1) + + # 极化损耗 + polarization_label = QLabel('极化损耗 (dB):') + self.polarization_edit = QDoubleSpinBox() + self.polarization_edit.setRange(0.0, 10.0) + self.polarization_edit.setValue(3.0) + system_layout.addWidget(polarization_label, 1, 2) + system_layout.addWidget(self.polarization_edit, 1, 3) + + # 雨衰 + rain_label = QLabel('雨衰 (dB):') + self.rain_edit = QDoubleSpinBox() + self.rain_edit.setRange(0.0, 20.0) + self.rain_edit.setValue(0.0) + system_layout.addWidget(rain_label, 2, 0) + system_layout.addWidget(self.rain_edit, 2, 1) + + # 大气损耗 + atm_label = QLabel('大气损耗 (dB):') + self.atm_edit = QDoubleSpinBox() + self.atm_edit.setRange(0.0, 10.0) + self.atm_edit.setValue(0.5) + system_layout.addWidget(atm_label, 2, 2) + system_layout.addWidget(self.atm_edit, 2, 3) + + design_layout.addWidget(system_group) + + # 创建发射机参数区域 + tx_group = QGroupBox('发射机参数') + tx_layout = QGridLayout(tx_group) + + # 发射功率 + tx_power_label = QLabel('发射功率 (dBm):') + self.tx_power_edit = QDoubleSpinBox() + self.tx_power_edit.setRange(-50.0, 50.0) + self.tx_power_edit.setValue(20.0) + tx_layout.addWidget(tx_power_label, 0, 0) + tx_layout.addWidget(self.tx_power_edit, 0, 1) + + # 发射机效率 + tx_efficiency_label = QLabel('发射机效率 (%):') + self.tx_efficiency_edit = QDoubleSpinBox() + self.tx_efficiency_edit.setRange(10.0, 100.0) + self.tx_efficiency_edit.setValue(80.0) + tx_layout.addWidget(tx_efficiency_label, 0, 2) + tx_layout.addWidget(self.tx_efficiency_edit, 0, 3) + + # 发射天线增益 + tx_antenna_gain_label = QLabel('发射天线增益 (dBi):') + self.tx_antenna_gain_edit = QDoubleSpinBox() + self.tx_antenna_gain_edit.setRange(-10.0, 50.0) + self.tx_antenna_gain_edit.setValue(10.0) + tx_layout.addWidget(tx_antenna_gain_label, 1, 0) + tx_layout.addWidget(self.tx_antenna_gain_edit, 1, 1) + + # 发射天线效率 + tx_antenna_efficiency_label = QLabel('发射天线效率 (%):') + self.tx_antenna_efficiency_edit = QDoubleSpinBox() + self.tx_antenna_efficiency_edit.setRange(50.0, 100.0) + self.tx_antenna_efficiency_edit.setValue(90.0) + tx_layout.addWidget(tx_antenna_efficiency_label, 1, 2) + tx_layout.addWidget(self.tx_antenna_efficiency_edit, 1, 3) + + design_layout.addWidget(tx_group) + + # 创建接收机参数区域 + rx_group = QGroupBox('接收机参数') + rx_layout = QGridLayout(rx_group) + + # 接收天线增益 + rx_antenna_gain_label = QLabel('接收天线增益 (dBi):') + self.rx_antenna_gain_edit = QDoubleSpinBox() + self.rx_antenna_gain_edit.setRange(-10.0, 50.0) + self.rx_antenna_gain_edit.setValue(10.0) + rx_layout.addWidget(rx_antenna_gain_label, 0, 0) + rx_layout.addWidget(self.rx_antenna_gain_edit, 0, 1) + + # 接收天线效率 + rx_antenna_efficiency_label = QLabel('接收天线效率 (%):') + self.rx_antenna_efficiency_edit = QDoubleSpinBox() + self.rx_antenna_efficiency_edit.setRange(50.0, 100.0) + self.rx_antenna_efficiency_edit.setValue(90.0) + rx_layout.addWidget(rx_antenna_efficiency_label, 0, 2) + rx_layout.addWidget(self.rx_antenna_efficiency_edit, 0, 3) + + # 接收机噪声系数 + rx_noise_label = QLabel('接收机噪声系数 (dB):') + self.rx_noise_edit = QDoubleSpinBox() + self.rx_noise_edit.setRange(1.0, 10.0) + self.rx_noise_edit.setValue(3.0) + rx_layout.addWidget(rx_noise_label, 1, 0) + rx_layout.addWidget(self.rx_noise_edit, 1, 1) + + # 接收机带宽 + rx_bandwidth_label = QLabel('接收机带宽 (MHz):') + self.rx_bandwidth_edit = QDoubleSpinBox() + self.rx_bandwidth_edit.setRange(0.1, 100.0) + self.rx_bandwidth_edit.setValue(20.0) + rx_layout.addWidget(rx_bandwidth_label, 1, 2) + rx_layout.addWidget(self.rx_bandwidth_edit, 1, 3) + + # 灵敏度要求 + sensitivity_label = QLabel('灵敏度要求 (dBm):') + self.sensitivity_edit = QDoubleSpinBox() + self.sensitivity_edit.setRange(-150.0, -50.0) + self.sensitivity_edit.setValue(-100.0) + rx_layout.addWidget(sensitivity_label, 2, 0) + rx_layout.addWidget(self.sensitivity_edit, 2, 1) + + design_layout.addWidget(rx_group) + + # 创建链路组件区域 + components_group = QGroupBox('链路组件') + components_layout = QVBoxLayout(components_group) + + # 创建组件表格 + self.components_table = QTableWidget() + self.components_table.setColumnCount(4) + self.components_table.setHorizontalHeaderLabels(['组件类型', '参数值', '单位', '备注']) + self.components_table.horizontalHeader().setStretchLastSection(True) + components_layout.addWidget(self.components_table) + + # 创建组件操作按钮 + component_button_layout = QHBoxLayout() + + # 添加组件按钮 + add_button = QPushButton('添加组件') + add_button.setIcon(QIcon('resources/icons/add.png')) + add_button.clicked.connect(self.add_component) + component_button_layout.addWidget(add_button) + + # 删除组件按钮 + delete_button = QPushButton('删除组件') + delete_button.setIcon(QIcon('resources/icons/delete.png')) + delete_button.clicked.connect(self.delete_component) + component_button_layout.addWidget(delete_button) + + # 清除组件按钮 + clear_components_button = QPushButton('清除组件') + clear_components_button.setIcon(QIcon('resources/icons/clear.png')) + clear_components_button.clicked.connect(self.clear_components) + component_button_layout.addWidget(clear_components_button) + + components_layout.addLayout(component_button_layout) + + design_layout.addWidget(components_group) + + # 创建计算按钮区域 + button_layout = QHBoxLayout() + + # 计算链路预算按钮 + calc_button = QPushButton('计算链路预算') + calc_button.setIcon(QIcon('resources/icons/calculate.png')) + calc_button.clicked.connect(self.calculate_link_budget) + button_layout.addWidget(calc_button) + + # 优化链路按钮 + optimize_button = QPushButton('优化链路') + optimize_button.setIcon(QIcon('resources/icons/optimize.png')) + optimize_button.clicked.connect(self.optimize_link) + button_layout.addWidget(optimize_button) + + # 清除按钮 + clear_button = QPushButton('清除') + clear_button.setIcon(QIcon('resources/icons/clear.png')) + clear_button.clicked.connect(self.clear) + button_layout.addWidget(clear_button) + + design_layout.addLayout(button_layout) + + # 创建结果显示区域 + result_group = QGroupBox('计算结果') + result_layout = QVBoxLayout(result_group) + + # 创建结果表格 + self.result_table = QTableWidget() + self.result_table.setColumnCount(3) + self.result_table.setHorizontalHeaderLabels(['参数名称', '数值', '单位']) + self.result_table.horizontalHeader().setStretchLastSection(True) + result_layout.addWidget(self.result_table) + + # 创建结果详情区域 + self.result_detail = QTextEdit() + self.result_detail.setReadOnly(True) + self.result_detail.setFont(QFont('Consolas', 10)) + result_layout.addWidget(self.result_detail) + + design_layout.addWidget(result_group) + + tab_widget.addTab(design_tab, '链路预算设计') + + # 创建分析标签 + analysis_tab = QWidget() + analysis_layout = QVBoxLayout(analysis_tab) + + # 创建可视化区域 + visual_group = QGroupBox('链路预算可视化') + visual_layout = QVBoxLayout(visual_group) + + # 创建图表画布 + self.figure = plt.figure(figsize=(8, 6)) + self.canvas = FigureCanvas(self.figure) + visual_layout.addWidget(self.canvas) + + # 图表控制按钮 + chart_button_layout = QHBoxLayout() + + # 绘制链路预算图按钮 + plot_link_button = QPushButton('绘制链路预算图') + plot_link_button.clicked.connect(self.plot_link_budget) + chart_button_layout.addWidget(plot_link_button) + + # 绘制参数扫描图按钮 + plot_scan_button = QPushButton('绘制参数扫描') + plot_scan_button.clicked.connect(self.plot_parameter_scan) + chart_button_layout.addWidget(plot_scan_button) + + # 清空图表按钮 + clear_chart_button = QPushButton('清空图表') + clear_chart_button.clicked.connect(self.clear_chart) + chart_button_layout.addWidget(clear_chart_button) + + visual_layout.addLayout(chart_button_layout) + + analysis_layout.addWidget(visual_group) + + tab_widget.addTab(analysis_tab, '链路分析') + + main_layout.addWidget(tab_widget) + + # 填充剩余空间 + main_layout.addStretch() + + def add_component(self): + # 添加链路组件 + component_types = ['放大器', '滤波器', '电缆', '功分器', '耦合器', '衰减器'] + + # 创建组件对话框 + dialog = QWidget() + dialog.setWindowTitle('添加链路组件') + dialog.setWindowModality(Qt.ApplicationModal) + + layout = QVBoxLayout(dialog) + + # 组件类型 + type_label = QLabel('组件类型:') + type_combo = QComboBox() + type_combo.addItems(component_types) + layout.addWidget(type_label) + layout.addWidget(type_combo) + + # 组件参数 + value_label = QLabel('参数值:') + value_edit = QDoubleSpinBox() + value_edit.setRange(-100.0, 100.0) + value_edit.setValue(0.0) + layout.addWidget(value_label) + layout.addWidget(value_edit) + + # 单位 + unit_label = QLabel('单位:') + unit_combo = QComboBox() + unit_combo.addItems(['dB', 'Ω', 'W']) + layout.addWidget(unit_label) + layout.addWidget(unit_combo) + + # 备注 + note_label = QLabel('备注:') + note_edit = QLineEdit() + layout.addWidget(note_label) + layout.addWidget(note_edit) + + # 确认按钮 + button_layout = QHBoxLayout() + ok_button = QPushButton('确定') + ok_button.clicked.connect(lambda: self.add_component_to_table(type_combo.currentText(), value_edit.value(), unit_combo.currentText(), note_edit.text(), dialog)) + button_layout.addWidget(ok_button) + + cancel_button = QPushButton('取消') + cancel_button.clicked.connect(dialog.close) + button_layout.addWidget(cancel_button) + + layout.addLayout(button_layout) + + dialog.exec_() + + def add_component_to_table(self, type_, value, unit, note, dialog): + # 添加组件到表格 + row = self.components_table.rowCount() + self.components_table.insertRow(row) + self.components_table.setItem(row, 0, QTableWidgetItem(type_)) + self.components_table.setItem(row, 1, QTableWidgetItem(f"{value:.2f}")) + self.components_table.setItem(row, 2, QTableWidgetItem(unit)) + self.components_table.setItem(row, 3, QTableWidgetItem(note)) + + dialog.close() + + def delete_component(self): + # 删除选中的组件 + current_row = self.components_table.currentRow() + if current_row >= 0: + self.components_table.removeRow(current_row) + else: + QMessageBox.warning(self, '警告', '请选择要删除的组件') + + def clear_components(self): + # 清除所有组件 + self.components_table.setRowCount(0) + + def calculate_link_budget(self): + # 计算链路预算 + try: + # 获取输入参数 + freq = self.freq_edit.value() * 1e9 # Hz + distance = self.distance_edit.value() * 1e3 # m + env = self.env_combo.currentText() + polarization_loss = self.polarization_edit.value() # dB + rain_loss = self.rain_edit.value() # dB + atm_loss = self.atm_edit.value() # dB + + # 发射机参数 + tx_power = self.tx_power_edit.value() # dBm + tx_efficiency = self.tx_efficiency_edit.value() / 100 # 小数 + tx_antenna_gain = self.tx_antenna_gain_edit.value() # dBi + tx_antenna_efficiency = self.tx_antenna_efficiency_edit.value() / 100 # 小数 + + # 接收机参数 + rx_antenna_gain = self.rx_antenna_gain_edit.value() # dBi + rx_antenna_efficiency = self.rx_antenna_efficiency_edit.value() / 100 # 小数 + rx_noise_figure = self.rx_noise_edit.value() # dB + rx_bandwidth = self.rx_bandwidth_edit.value() * 1e6 # Hz + sensitivity_req = self.sensitivity_edit.value() # dBm + + # 计算自由空间路径损耗 + fspl = self.calculate_fspl(freq, distance) + + # 计算传播损耗(考虑环境因素) + propagation_loss = self.calculate_propagation_loss(fspl, env, freq, distance) + + # 计算总路径损耗 + total_path_loss = propagation_loss + polarization_loss + rain_loss + atm_loss + + # 计算天线增益(考虑效率) + tx_antenna_gain_eff = tx_antenna_gain + 10 * np.log10(tx_antenna_efficiency) + rx_antenna_gain_eff = rx_antenna_gain + 10 * np.log10(rx_antenna_efficiency) + + # 计算链路组件损耗/增益 + component_loss = 0.0 + components = [] + for row in range(self.components_table.rowCount()): + type_ = self.components_table.item(row, 0).text() + value = float(self.components_table.item(row, 1).text()) + unit = self.components_table.item(row, 2).text() + note = self.components_table.item(row, 3).text() + + # 转换为dB + if unit == 'dB': + component_loss += value + elif unit == 'W': + component_loss += 10 * np.log10(value / 0.001) # 转换为dBm + + components.append({ + 'type': type_, + 'value': value, + 'unit': unit, + 'note': note, + 'loss': value if unit == 'dB' else 10 * np.log10(value / 0.001) + }) + + # 计算发射功率(考虑效率) + tx_power_eff = tx_power + 10 * np.log10(tx_efficiency) + + # 计算接收功率 + received_power = tx_power_eff + tx_antenna_gain_eff - total_path_loss - component_loss + rx_antenna_gain_eff + + # 计算接收机灵敏度 + noise_power = -174 + 10 * np.log10(rx_bandwidth) + rx_noise_figure # dBm + sensitivity = noise_power + 6 # 假设SNR要求为6dB + + # 计算链路余量 + link_margin = received_power - sensitivity_req + + # 计算SNR + snr = received_power - noise_power + + # 保存结果 + result = { + 'freq': freq, + 'distance': distance, + 'env': env, + 'polarization_loss': polarization_loss, + 'rain_loss': rain_loss, + 'atm_loss': atm_loss, + 'tx_power': tx_power, + 'tx_efficiency': tx_efficiency, + 'tx_antenna_gain': tx_antenna_gain, + 'tx_antenna_efficiency': tx_antenna_efficiency, + 'rx_antenna_gain': rx_antenna_gain, + 'rx_antenna_efficiency': rx_antenna_efficiency, + 'rx_noise_figure': rx_noise_figure, + 'rx_bandwidth': rx_bandwidth, + 'sensitivity_req': sensitivity_req, + 'fspl': fspl, + 'propagation_loss': propagation_loss, + 'total_path_loss': total_path_loss, + 'tx_antenna_gain_eff': tx_antenna_gain_eff, + 'rx_antenna_gain_eff': rx_antenna_gain_eff, + 'component_loss': component_loss, + 'tx_power_eff': tx_power_eff, + 'received_power': received_power, + 'noise_power': noise_power, + 'sensitivity': sensitivity, + 'link_margin': link_margin, + 'snr': snr, + 'components': components + } + + self.link_results.append(result) + + # 显示结果 + self.display_results(result) + + # 保存到历史 + self.history_manager.add_history( + tool_name='射频链路预算分析器', + input_data=f"频率: {freq/1e9} GHz, 距离: {distance/1e3} km, 发射功率: {tx_power} dBm", + output_data=f"接收功率: {received_power:.2f} dBm, 链路余量: {link_margin:.2f} dB, SNR: {snr:.2f} dB", + calculation_type='link_budget' + ) + + except Exception as e: + QMessageBox.warning(self, '警告', f'计算错误: {str(e)}') + + def calculate_fspl(self, freq, distance): + # 计算自由空间路径损耗 (dB) + c0 = 299792458 # 真空中的光速 + wavelength = c0 / freq + fspl = 20 * np.log10(distance) + 20 * np.log10(freq) + 20 * np.log10(4 * np.pi / c0) + return fspl + + def calculate_propagation_loss(self, fspl, env, freq, distance): + # 计算传播损耗(考虑环境因素) + # 基于Okumura-Hata模型的简化版本 + if env == '自由空间': + return fspl + elif env == '市区': + # 市区环境增加20-30dB损耗 + return fspl + 25 + elif env == '郊区': + # 郊区环境增加10-20dB损耗 + return fspl + 15 + elif env == '农村': + # 农村环境增加5-10dB损耗 + return fspl + 7 + elif env == '室内': + # 室内环境增加15-30dB损耗 + return fspl + 20 + else: + return fspl + + def optimize_link(self): + # 优化链路预算 + try: + # 获取当前计算结果 + if not self.link_results: + QMessageBox.warning(self, '警告', '没有计算结果可以优化') + return + + current_result = self.link_results[-1] + + # 检查链路余量是否足够 + if current_result['link_margin'] >= 10: + QMessageBox.information(self, '优化完成', '当前链路余量已经足够(>10dB),无需优化') + return + + # 优化建议 + suggestions = [] + + # 1. 增加发射功率 + if current_result['tx_power'] < 30: # 假设最大发射功率为30dBm + new_tx_power = current_result['tx_power'] + 5 + suggestions.append(f"增加发射功率到 {new_tx_power} dBm") + + # 2. 增加天线增益 + if current_result['tx_antenna_gain'] < 20: + new_tx_gain = current_result['tx_antenna_gain'] + 5 + suggestions.append(f"增加发射天线增益到 {new_tx_gain} dBi") + + if current_result['rx_antenna_gain'] < 20: + new_rx_gain = current_result['rx_antenna_gain'] + 5 + suggestions.append(f"增加接收天线增益到 {new_rx_gain} dBi") + + # 3. 减少链路损耗 + if current_result['component_loss'] > 0: + suggestions.append("减少链路组件损耗") + + # 4. 提高天线效率 + if current_result['tx_antenna_efficiency'] < 0.95: + suggestions.append("提高发射天线效率") + + if current_result['rx_antenna_efficiency'] < 0.95: + suggestions.append("提高接收天线效率") + + # 5. 降低接收机噪声系数 + if current_result['rx_noise_figure'] > 1.5: + suggestions.append("降低接收机噪声系数") + + # 显示优化建议 + if suggestions: + suggestion_text = "链路优化建议:\n\n" + for i, suggestion in enumerate(suggestions, 1): + suggestion_text += f"{i}. {suggestion}\n" + + QMessageBox.information(self, '优化建议', suggestion_text) + else: + QMessageBox.information(self, '优化完成', '当前链路已经无法进一步优化') + + except Exception as e: + QMessageBox.warning(self, '警告', f'优化错误: {str(e)}') + + def display_results(self, result): + # 显示计算结果 + self.result_table.setRowCount(0) + + # 显示链路预算结果 + detail_text = "射频链路预算计算结果:\n\n" + + # 系统参数 + detail_text += "系统参数:\n" + detail_text += f"频率: {result['freq']/1e9:.2f} GHz\n" + detail_text += f"距离: {result['distance']/1e3:.2f} km\n" + detail_text += f"传播环境: {result['env']}\n" + detail_text += f"极化损耗: {result['polarization_loss']:.2f} dB\n" + detail_text += f"雨衰: {result['rain_loss']:.2f} dB\n" + detail_text += f"大气损耗: {result['atm_loss']:.2f} dB\n\n" + + # 发射机参数 + detail_text += "发射机参数:\n" + detail_text += f"发射功率: {result['tx_power']:.2f} dBm\n" + detail_text += f"发射机效率: {result['tx_efficiency']*100:.2f}%\n" + detail_text += f"发射天线增益: {result['tx_antenna_gain']:.2f} dBi\n" + detail_text += f"发射天线效率: {result['tx_antenna_efficiency']*100:.2f}%\n" + detail_text += f"有效发射天线增益: {result['tx_antenna_gain_eff']:.2f} dBi\n" + detail_text += f"有效发射功率: {result['tx_power_eff']:.2f} dBm\n\n" + + # 接收机参数 + detail_text += "接收机参数:\n" + detail_text += f"接收天线增益: {result['rx_antenna_gain']:.2f} dBi\n" + detail_text += f"接收天线效率: {result['rx_antenna_efficiency']*100:.2f}%\n" + detail_text += f"有效接收天线增益: {result['rx_antenna_gain_eff']:.2f} dBi\n" + detail_text += f"接收机噪声系数: {result['rx_noise_figure']:.2f} dB\n" + detail_text += f"接收机带宽: {result['rx_bandwidth']/1e6:.2f} MHz\n" + detail_text += f"灵敏度要求: {result['sensitivity_req']:.2f} dBm\n" + detail_text += f"计算灵敏度: {result['sensitivity']:.2f} dBm\n" + detail_text += f"噪声功率: {result['noise_power']:.2f} dBm\n\n" + + # 路径损耗 + detail_text += "路径损耗:\n" + detail_text += f"自由空间路径损耗: {result['fspl']:.2f} dB\n" + detail_text += f"传播损耗: {result['propagation_loss']:.2f} dB\n" + detail_text += f"总路径损耗: {result['total_path_loss']:.2f} dB\n\n" + + # 链路组件 + if result['components']: + detail_text += "链路组件损耗:\n" + for i, component in enumerate(result['components']): + detail_text += f"{component['type']}: {component['loss']:.2f} dB ({component['note']})\n" + detail_text += f"总组件损耗: {result['component_loss']:.2f} dB\n\n" + + # 最终结果 + detail_text += "最终结果:\n" + detail_text += f"接收功率: {result['received_power']:.2f} dBm\n" + detail_text += f"链路余量: {result['link_margin']:.2f} dB\n" + detail_text += f"信噪比 (SNR): {result['snr']:.2f} dB\n\n" + + # 添加到结果表格 + self.add_result_row('频率', result['freq']/1e9, 'GHz') + self.add_result_row('距离', result['distance']/1e3, 'km') + self.add_result_row('传播环境', result['env'], '') + self.add_result_row('总路径损耗', result['total_path_loss'], 'dB') + self.add_result_row('有效发射功率', result['tx_power_eff'], 'dBm') + self.add_result_row('有效发射天线增益', result['tx_antenna_gain_eff'], 'dBi') + self.add_result_row('有效接收天线增益', result['rx_antenna_gain_eff'], 'dBi') + self.add_result_row('总组件损耗', result['component_loss'], 'dB') + self.add_result_row('接收功率', result['received_power'], 'dBm') + self.add_result_row('噪声功率', result['noise_power'], 'dBm') + self.add_result_row('计算灵敏度', result['sensitivity'], 'dBm') + self.add_result_row('灵敏度要求', result['sensitivity_req'], 'dBm') + self.add_result_row('链路余量', result['link_margin'], 'dB') + self.add_result_row('信噪比', result['snr'], 'dB') + + self.result_detail.setText(detail_text) + + def add_result_row(self, name, value, unit): + # 添加结果行到表格 + row = self.result_table.rowCount() + self.result_table.insertRow(row) + self.result_table.setItem(row, 0, QTableWidgetItem(name)) + self.result_table.setItem(row, 1, QTableWidgetItem(f"{value:.2f}" if isinstance(value, float) else str(value))) + self.result_table.setItem(row, 2, QTableWidgetItem(unit)) + + def plot_link_budget(self): + # 绘制链路预算图 + if not self.link_results: + QMessageBox.warning(self, '警告', '没有计算结果可以绘制') + return + + # 获取最新的计算结果 + result = self.link_results[-1] + + # 创建链路预算数据 + stages = ['发射功率', '发射天线增益', '路径损耗', '组件损耗', '接收天线增益', '接收功率'] + values = [ + result['tx_power_eff'], + result['tx_antenna_gain_eff'], + -result['total_path_loss'], + -result['component_loss'], + result['rx_antenna_gain_eff'], + result['received_power'] + ] + + # 计算累积功率 + cumulative = [result['tx_power_eff']] + for i in range(1, len(values)): + cumulative.append(cumulative[-1] + values[i]) + + # 绘制链路预算图 + self.figure.clear() + ax = self.figure.add_subplot(111) + + # 绘制累积功率曲线 + ax.plot(range(len(stages)), cumulative, marker='o', linewidth=2, markersize=8) + + # 添加标签 + for i, (stage, value, cum) in enumerate(zip(stages, values, cumulative)): + ax.text(i, cum + 0.5, f'{cum:.2f} dBm', ha='center', va='bottom') + if i > 0: + ax.text(i - 0.5, (cumulative[i-1] + cum)/2, f'{value:+.2f} dB', ha='center', va='center', backgroundcolor='white') + + ax.set_xticks(range(len(stages))) + ax.set_xticklabels(stages, rotation=45, ha='right') + ax.set_title('射频链路预算分析') + ax.set_ylabel('功率 (dBm)') + ax.grid(True) + + # 添加水平线显示灵敏度要求 + ax.axhline(result['sensitivity_req'], color='red', linestyle='--', label=f'灵敏度要求: {result["sensitivity_req"]:.2f} dBm') + ax.legend() + + self.figure.tight_layout() + self.canvas.draw() + + def plot_parameter_scan(self): + # 绘制参数扫描图 + if not self.link_results: + QMessageBox.warning(self, '警告', '没有计算结果可以绘制') + return + + # 获取最新的计算结果 + result = self.link_results[-1] + + # 扫描距离对链路余量的影响 + distances = np.linspace(0.1, 10.0, 50) # km + link_margins = [] + + for d in distances: + # 重新计算链路预算 + distance_m = d * 1e3 + fspl = self.calculate_fspl(result['freq'], distance_m) + propagation_loss = self.calculate_propagation_loss(fspl, result['env'], result['freq'], distance_m) + total_path_loss = propagation_loss + result['polarization_loss'] + result['rain_loss'] + result['atm_loss'] + + received_power = result['tx_power_eff'] + result['tx_antenna_gain_eff'] - total_path_loss - result['component_loss'] + result['rx_antenna_gain_eff'] + link_margin = received_power - result['sensitivity_req'] + link_margins.append(link_margin) + + # 绘制参数扫描图 + self.figure.clear() + ax = self.figure.add_subplot(111) + ax.plot(distances, link_margins) + ax.set_title('距离对链路余量的影响') + ax.set_xlabel('距离 (km)') + ax.set_ylabel('链路余量 (dB)') + ax.grid(True) + + # 添加水平线显示最小链路余量要求 + ax.axhline(10, color='red', linestyle='--', label='最小链路余量要求: 10 dB') + ax.legend() + + self.canvas.draw() + + def clear_chart(self): + # 清空图表 + self.figure.clear() + self.canvas.draw() + + def clear(self): + # 清空输入和结果 + self.freq_edit.setValue(2.4) + self.distance_edit.setValue(1.0) + self.env_combo.setCurrentText('自由空间') + self.polarization_edit.setValue(3.0) + self.rain_edit.setValue(0.0) + self.atm_edit.setValue(0.5) + self.tx_power_edit.setValue(20.0) + self.tx_efficiency_edit.setValue(80.0) + self.tx_antenna_gain_edit.setValue(10.0) + self.tx_antenna_efficiency_edit.setValue(90.0) + self.rx_antenna_gain_edit.setValue(10.0) + self.rx_antenna_efficiency_edit.setValue(90.0) + self.rx_noise_edit.setValue(3.0) + self.rx_bandwidth_edit.setValue(20.0) + self.sensitivity_edit.setValue(-100.0) + self.clear_components() + self.result_table.setRowCount(0) + self.result_detail.clear() + self.clear_chart() \ No newline at end of file diff --git a/tools/power_converter.py b/tools/power_converter.py new file mode 100644 index 0000000..3cd4ef3 --- /dev/null +++ b/tools/power_converter.py @@ -0,0 +1,561 @@ +import sys +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QComboBox, QTableWidget, QTableWidgetItem, QTextEdit, QGroupBox, QGridLayout, QSpinBox, QDoubleSpinBox, QMessageBox +from PyQt5.QtGui import QFont, QIcon +from PyQt5.QtCore import Qt, pyqtSignal +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from data.history_manager import HistoryManager + +class PowerConverter(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + self.history_manager = HistoryManager() + self.calculation_results = [] + + def init_ui(self): + # 创建主布局 + main_layout = QVBoxLayout(self) + + # 创建标题 + title_label = QLabel('功率单位换算器') + title_label.setFont(QFont('Consolas', 16, QFont.Bold)) + main_layout.addWidget(title_label) + + # 创建换算区域 + convert_group = QGroupBox('功率换算') + convert_layout = QGridLayout(convert_group) + + # 功率输入 + power_label = QLabel('功率:') + self.power_edit = QLineEdit() + self.power_edit.setPlaceholderText('输入功率值') + self.power_edit.textChanged.connect(self.real_time_convert) + power_unit_label = QLabel('单位:') + self.power_unit_combo = QComboBox() + self.power_unit_combo.addItems(['W', 'mW', 'μW', 'nW']) + self.power_unit_combo.currentTextChanged.connect(self.real_time_convert) + + convert_layout.addWidget(power_label, 0, 0) + convert_layout.addWidget(self.power_edit, 0, 1) + convert_layout.addWidget(power_unit_label, 0, 2) + convert_layout.addWidget(self.power_unit_combo, 0, 3) + + # dB值输入 + db_label = QLabel('dB值:') + self.db_edit = QLineEdit() + self.db_edit.setPlaceholderText('输入dB值') + self.db_edit.textChanged.connect(self.real_time_convert) + db_unit_label = QLabel('单位:') + self.db_unit_combo = QComboBox() + self.db_unit_combo.addItems(['dBm', 'dBW']) + self.db_unit_combo.currentTextChanged.connect(self.real_time_convert) + + convert_layout.addWidget(db_label, 1, 0) + convert_layout.addWidget(self.db_edit, 1, 1) + convert_layout.addWidget(db_unit_label, 1, 2) + convert_layout.addWidget(self.db_unit_combo, 1, 3) + + # 换算方向 + direction_label = QLabel('换算方向:') + self.direction_combo = QComboBox() + self.direction_combo.addItems(['功率 → dB', 'dB → 功率', '双向实时']) + self.direction_combo.currentTextChanged.connect(self.real_time_convert) + + convert_layout.addWidget(direction_label, 2, 0) + convert_layout.addWidget(self.direction_combo, 2, 1, 1, 3) + + main_layout.addWidget(convert_group) + + # 创建结果显示区域 + result_group = QGroupBox('换算结果') + result_layout = QVBoxLayout(result_group) + + # 创建结果标签 + self.result_label = QLabel('') + self.result_label.setFont(QFont('Consolas', 12)) + self.result_label.setAlignment(Qt.AlignCenter) + result_layout.addWidget(self.result_label) + + # 创建详细结果文本框 + self.result_detail = QTextEdit() + self.result_detail.setReadOnly(True) + self.result_detail.setFont(QFont('Consolas', 10)) + result_layout.addWidget(self.result_detail) + + main_layout.addWidget(result_group) + + # 创建链路预算区域 + budget_group = QGroupBox('链路预算分析') + budget_layout = QVBoxLayout(budget_group) + + # 创建链路预算表格 + self.budget_table = QTableWidget() + self.budget_table.setColumnCount(4) + self.budget_table.setHorizontalHeaderLabels(['部件名称', '增益/损耗(dB)', '类型', '备注']) + self.budget_table.horizontalHeader().setStretchLastSection(True) + + # 添加默认行 + self.budget_table.setRowCount(3) + self.budget_table.setItem(0, 0, QTableWidgetItem('发射机功率')) + self.budget_table.setItem(0, 1, QTableWidgetItem('30')) + self.budget_table.setItem(0, 2, QTableWidgetItem('增益')) + self.budget_table.setItem(0, 3, QTableWidgetItem('dBm')) + + self.budget_table.setItem(1, 0, QTableWidgetItem('传输损耗')) + self.budget_table.setItem(1, 1, QTableWidgetItem('-10')) + self.budget_table.setItem(1, 2, QTableWidgetItem('损耗')) + self.budget_table.setItem(1, 3, QTableWidgetItem('自由空间损耗')) + + self.budget_table.setItem(2, 0, QTableWidgetItem('接收机灵敏度')) + self.budget_table.setItem(2, 1, QTableWidgetItem('-90')) + self.budget_table.setItem(2, 2, QTableWidgetItem('损耗')) + self.budget_table.setItem(2, 3, QTableWidgetItem('dBm')) + + budget_layout.addWidget(self.budget_table) + + # 链路预算按钮 + budget_button_layout = QHBoxLayout() + + # 添加部件按钮 + add_button = QPushButton('添加部件') + add_button.setIcon(QIcon('resources/icons/add.png')) + add_button.clicked.connect(self.add_budget_item) + budget_button_layout.addWidget(add_button) + + # 删除部件按钮 + delete_button = QPushButton('删除部件') + delete_button.setIcon(QIcon('resources/icons/delete.png')) + delete_button.clicked.connect(self.delete_budget_item) + budget_button_layout.addWidget(delete_button) + + # 计算链路预算按钮 + calc_budget_button = QPushButton('计算链路预算') + calc_budget_button.setIcon(QIcon('resources/icons/calculate.png')) + calc_budget_button.clicked.connect(self.calculate_link_budget) + budget_button_layout.addWidget(calc_budget_button) + + budget_layout.addLayout(budget_button_layout) + + # 链路预算结果 + self.budget_result = QTextEdit() + self.budget_result.setReadOnly(True) + self.budget_result.setFont(QFont('Consolas', 10)) + budget_layout.addWidget(self.budget_result) + + main_layout.addWidget(budget_group) + + # 创建批量计算区域 + batch_group = QGroupBox('批量计算') + batch_layout = QVBoxLayout(batch_group) + + # 批量输入 + batch_label = QLabel('批量输入(逗号分隔):') + self.batch_edit = QLineEdit() + self.batch_edit.setPlaceholderText('输入多个功率值,逗号分隔') + + batch_layout.addWidget(batch_label) + batch_layout.addWidget(self.batch_edit) + + # 批量计算按钮 + batch_button = QPushButton('批量计算') + batch_button.setIcon(QIcon('resources/icons/batch.png')) + batch_button.clicked.connect(self.batch_calculate) + batch_layout.addWidget(batch_button) + + # 批量结果表格 + self.batch_table = QTableWidget() + self.batch_table.setColumnCount(4) + self.batch_table.setHorizontalHeaderLabels(['输入值', '输入单位', '输出值', '输出单位']) + self.batch_table.horizontalHeader().setStretchLastSection(True) + batch_layout.addWidget(self.batch_table) + + main_layout.addWidget(batch_group) + + # 创建可视化区域 + visual_group = QGroupBox('可视化') + visual_layout = QVBoxLayout(visual_group) + + # 创建图表画布 + self.figure, self.ax = plt.subplots(figsize=(8, 4)) + self.canvas = FigureCanvas(self.figure) + visual_layout.addWidget(self.canvas) + + # 图表控制按钮 + chart_button_layout = QHBoxLayout() + + # 绘制功率分布按钮 + power_chart_button = QPushButton('绘制功率分布') + power_chart_button.clicked.connect(self.plot_power_distribution) + chart_button_layout.addWidget(power_chart_button) + + # 绘制dB分布按钮 + db_chart_button = QPushButton('绘制dB分布') + db_chart_button.clicked.connect(self.plot_db_distribution) + chart_button_layout.addWidget(db_chart_button) + + # 清空图表按钮 + clear_chart_button = QPushButton('清空图表') + clear_chart_button.clicked.connect(self.clear_chart) + chart_button_layout.addWidget(clear_chart_button) + + visual_layout.addLayout(chart_button_layout) + + main_layout.addWidget(visual_group) + + # 填充剩余空间 + main_layout.addStretch() + + def real_time_convert(self): + # 实时换算功率和dB值 + power_text = self.power_edit.text().strip() + db_text = self.db_edit.text().strip() + direction = self.direction_combo.currentText() + + # 检查输入 + if not power_text and not db_text: + self.result_label.setText('') + self.result_detail.setText('') + return + + try: + if direction == '功率 → dB' or (direction == '双向实时' and power_text): + # 从功率转换为dB + power = float(power_text) + power_unit = self.power_unit_combo.currentText() + + # 转换为W + if power_unit == 'mW': + power_w = power / 1000 + elif power_unit == 'μW': + power_w = power / 1e6 + elif power_unit == 'nW': + power_w = power / 1e9 + else: + power_w = power + + # 计算dB值 + if self.db_unit_combo.currentText() == 'dBm': + # dBm = 10 * log10(power_mW) + power_mw = power_w * 1000 + if power_mw <= 0: + self.result_label.setText('功率值必须大于0') + return + db_value = 10 * np.log10(power_mw) + else: + # dBW = 10 * log10(power_W) + if power_w <= 0: + self.result_label.setText('功率值必须大于0') + return + db_value = 10 * np.log10(power_w) + + # 显示结果 + self.db_edit.setText(f"{db_value:.6f}") + self.display_result(power, power_unit, db_value, self.db_unit_combo.currentText()) + + # 保存到历史 + self.history_manager.add_history( + tool_name='功率单位换算器', + input_data=f"{power} {power_unit}", + output_data=f"{db_value:.6f} {self.db_unit_combo.currentText()}", + calculation_type='power_to_db' + ) + + elif direction == 'dB → 功率' or (direction == '双向实时' and db_text): + # 从dB转换为功率 + db_value = float(db_text) + db_unit = self.db_unit_combo.currentText() + + # 计算功率 + if db_unit == 'dBm': + # power_mW = 10^(dBm / 10) + power_mw = 10 ** (db_value / 10) + power_w = power_mw / 1000 + else: + # power_W = 10^(dBW / 10) + power_w = 10 ** (db_value / 10) + + # 转换为选择的功率单位 + power_unit = self.power_unit_combo.currentText() + if power_unit == 'mW': + power = power_w * 1000 + elif power_unit == 'μW': + power = power_w * 1e6 + elif power_unit == 'nW': + power = power_w * 1e9 + else: + power = power_w + + # 显示结果 + self.power_edit.setText(f"{power:.6e}") + self.display_result(power, power_unit, db_value, db_unit) + + # 保存到历史 + self.history_manager.add_history( + tool_name='功率单位换算器', + input_data=f"{db_value} {db_unit}", + output_data=f"{power:.6e} {power_unit}", + calculation_type='db_to_power' + ) + + except ValueError: + self.result_label.setText('输入格式错误') + + def display_result(self, power, power_unit, db_value, db_unit): + # 显示换算结果 + result_text = f"{power:.6e} {power_unit} = {db_value:.6f} {db_unit}" + self.result_label.setText(result_text) + + # 显示详细结果 + detail_text = "换算详情:\n\n" + detail_text += f"输入值: {power:.6e} {power_unit}\n" + detail_text += f"输出值: {db_value:.6f} {db_unit}\n" + detail_text += f"换算方向: {self.direction_combo.currentText()}\n" + detail_text += f"换算时间: 刚刚\n" + + self.result_detail.setText(detail_text) + + # 保存到计算结果列表 + self.calculation_results.append({ + 'power': power, + 'power_unit': power_unit, + 'db_value': db_value, + 'db_unit': db_unit + }) + + def calculate_link_budget(self): + # 计算链路预算 + total_gain = 0.0 + transmit_power = 0.0 + receive_sensitivity = 0.0 + + # 遍历表格中的所有行 + for row in range(self.budget_table.rowCount()): + try: + gain_loss = float(self.budget_table.item(row, 1).text()) + component_type = self.budget_table.item(row, 2).text() + component_name = self.budget_table.item(row, 0).text() + + # 累加增益/损耗 + if component_type == '增益': + total_gain += gain_loss + else: + total_gain += gain_loss # 损耗已经是负值 + + # 记录发射功率和接收机灵敏度 + if component_name == '发射机功率': + transmit_power = gain_loss + elif component_name == '接收机灵敏度': + receive_sensitivity = gain_loss + + except (ValueError, AttributeError): + continue + + # 计算接收功率 + receive_power = transmit_power + total_gain - transmit_power # 去除发射机功率的重复计算 + + # 计算链路余量 + link_margin = receive_power - receive_sensitivity + + # 显示结果 + result_text = "链路预算结果:\n\n" + result_text += f"发射机功率: {transmit_power:.2f} dBm\n" + result_text += f"总增益/损耗: {total_gain - transmit_power:.2f} dB\n" + result_text += f"接收功率: {receive_power:.2f} dBm\n" + result_text += f"接收机灵敏度: {receive_sensitivity:.2f} dBm\n" + result_text += f"链路余量: {link_margin:.2f} dB\n\n" + + # 判断链路是否可行 + if link_margin >= 0: + result_text += "✅ 链路可行,有足够的余量" + else: + result_text += "❌ 链路不可行,余量不足" + + self.budget_result.setText(result_text) + + # 保存到历史 + self.history_manager.add_history( + tool_name='功率单位换算器', + input_data=f"发射机功率: {transmit_power} dBm, 总增益/损耗: {total_gain - transmit_power} dB", + output_data=f"接收功率: {receive_power} dBm, 链路余量: {link_margin} dB", + calculation_type='link_budget' + ) + + def add_budget_item(self): + # 添加链路预算部件 + row = self.budget_table.rowCount() + self.budget_table.insertRow(row) + self.budget_table.setItem(row, 0, QTableWidgetItem('新部件')) + self.budget_table.setItem(row, 1, QTableWidgetItem('0')) + self.budget_table.setItem(row, 2, QTableWidgetItem('增益')) + self.budget_table.setItem(row, 3, QTableWidgetItem('')) + + def delete_budget_item(self): + # 删除链路预算部件 + selected_rows = self.budget_table.selectedItems() + if not selected_rows: + QMessageBox.warning(self, '警告', '请选择要删除的部件') + return + + # 去重行号 + rows = list(set(item.row() for item in selected_rows)) + + # 按降序删除行 + for row in sorted(rows, reverse=True): + self.budget_table.removeRow(row) + + def batch_calculate(self): + # 批量计算 + batch_text = self.batch_edit.text().strip() + if not batch_text: + QMessageBox.warning(self, '警告', '请输入批量计算值') + return + + try: + # 解析输入 + inputs = [float(v) for v in batch_text.split(',')] + direction = self.direction_combo.currentText().split('→')[0].strip() + + # 执行批量计算 + results = [] + for input_val in inputs: + if direction == '功率': + # 功率 → dB + power = input_val + power_unit = self.power_unit_combo.currentText() + + # 转换为W + if power_unit == 'mW': + power_w = power / 1000 + elif power_unit == 'μW': + power_w = power / 1e6 + elif power_unit == 'nW': + power_w = power / 1e9 + else: + power_w = power + + # 计算dB值 + if self.db_unit_combo.currentText() == 'dBm': + power_mw = power_w * 1000 + if power_mw <= 0: + db_value = '无效值' + else: + db_value = 10 * np.log10(power_mw) + else: + if power_w <= 0: + db_value = '无效值' + else: + db_value = 10 * np.log10(power_w) + + results.append({ + 'input': input_val, + 'input_unit': power_unit, + 'output': db_value, + 'output_unit': self.db_unit_combo.currentText() + }) + + else: + # dB → 功率 + db_value = input_val + db_unit = self.db_unit_combo.currentText() + + # 计算功率 + if db_unit == 'dBm': + power_mw = 10 ** (db_value / 10) + power_w = power_mw / 1000 + else: + power_w = 10 ** (db_value / 10) + + # 转换为选择的功率单位 + power_unit = self.power_unit_combo.currentText() + if power_unit == 'mW': + power = power_w * 1000 + elif power_unit == 'μW': + power = power_w * 1e6 + elif power_unit == 'nW': + power = power_w * 1e9 + else: + power = power_w + + results.append({ + 'input': input_val, + 'input_unit': db_unit, + 'output': power, + 'output_unit': power_unit + }) + + # 显示结果 + self.batch_table.setRowCount(len(results)) + for i, result in enumerate(results): + self.batch_table.setItem(i, 0, QTableWidgetItem(f"{result['input']:.6e}")) + self.batch_table.setItem(i, 1, QTableWidgetItem(result['input_unit'])) + if isinstance(result['output'], str): + self.batch_table.setItem(i, 2, QTableWidgetItem(result['output'])) + else: + self.batch_table.setItem(i, 2, QTableWidgetItem(f"{result['output']:.6e}")) + self.batch_table.setItem(i, 3, QTableWidgetItem(result['output_unit'])) + + # 保存到历史 + for result in results: + self.history_manager.add_history( + tool_name='功率单位换算器', + input_data=f"{result['input']} {result['input_unit']}", + output_data=f"{result['output']} {result['output_unit']}", + calculation_type='batch_calculate' + ) + + except ValueError: + QMessageBox.warning(self, '警告', '批量输入格式错误') + + def plot_power_distribution(self): + # 绘制功率分布图表 + if not self.calculation_results: + QMessageBox.warning(self, '警告', '没有计算结果可以绘制') + return + + # 提取功率数据 + powers = [] + for result in self.calculation_results: + # 转换为W + power = result['power'] + unit = result['power_unit'] + if unit == 'mW': + power /= 1000 + elif unit == 'μW': + power /= 1e6 + elif unit == 'nW': + power /= 1e9 + powers.append(power) + + if powers: + self.ax.clear() + self.ax.hist(powers, bins=20, alpha=0.75) + self.ax.set_xlabel('功率 (W)') + self.ax.set_ylabel('数量') + self.ax.set_title('功率分布直方图') + self.ax.grid(True) + self.canvas.draw() + + def plot_db_distribution(self): + # 绘制dB分布图表 + if not self.calculation_results: + QMessageBox.warning(self, '警告', '没有计算结果可以绘制') + return + + # 提取dB数据 + db_values = [result['db_value'] for result in self.calculation_results if isinstance(result['db_value'], float)] + + if db_values: + self.ax.clear() + self.ax.hist(db_values, bins=20, alpha=0.75) + self.ax.set_xlabel('dB值') + self.ax.set_ylabel('数量') + self.ax.set_title('dB值分布直方图') + self.ax.grid(True) + self.canvas.draw() + + def clear_chart(self): + # 清空图表 + self.ax.clear() + self.canvas.draw() \ No newline at end of file diff --git a/tools/transmission_line.py b/tools/transmission_line.py new file mode 100644 index 0000000..0903f5f --- /dev/null +++ b/tools/transmission_line.py @@ -0,0 +1,597 @@ +import sys +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QComboBox, QTableWidget, QTableWidgetItem, QTextEdit, QGroupBox, QGridLayout, QDoubleSpinBox, QMessageBox, QSplitter +from PyQt5.QtGui import QFont, QIcon +from PyQt5.QtCore import Qt +from data.history_manager import HistoryManager + +class TransmissionLineCalculator(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + self.history_manager = HistoryManager() + self.calculation_results = [] + + def init_ui(self): + # 创建主布局 + main_layout = QVBoxLayout(self) + + # 创建标题 + title_label = QLabel('传输线参数计算器') + title_label.setFont(QFont('Consolas', 16, QFont.Bold)) + main_layout.addWidget(title_label) + + # 创建输入区域 + input_group = QGroupBox('传输线参数') + input_layout = QGridLayout(input_group) + + # 频率输入 + freq_label = QLabel('频率 (GHz):') + self.freq_edit = QDoubleSpinBox() + self.freq_edit.setRange(0.1, 100.0) + self.freq_edit.setValue(2.4) + input_layout.addWidget(freq_label, 0, 0) + input_layout.addWidget(self.freq_edit, 0, 1) + + # 传输线类型 + line_type_label = QLabel('传输线类型:') + self.line_type_combo = QComboBox() + self.line_type_combo.addItems(['微带线', '带状线', '同轴线', '波导']) + self.line_type_combo.currentTextChanged.connect(self.update_line_params) + input_layout.addWidget(line_type_label, 0, 2) + input_layout.addWidget(self.line_type_combo, 0, 3) + + # 特性阻抗 + z0_label = QLabel('特性阻抗 (Ω):') + self.z0_edit = QDoubleSpinBox() + self.z0_edit.setRange(10.0, 200.0) + self.z0_edit.setValue(50.0) + input_layout.addWidget(z0_label, 1, 0) + input_layout.addWidget(self.z0_edit, 1, 1) + + # 负载阻抗 + zl_label = QLabel('负载阻抗 (Ω):') + self.zl_real_edit = QDoubleSpinBox() + self.zl_real_edit.setRange(1.0, 1000.0) + self.zl_real_edit.setValue(75.0) + self.zl_imag_edit = QDoubleSpinBox() + self.zl_imag_edit.setRange(-1000.0, 1000.0) + self.zl_imag_edit.setValue(0.0) + zl_layout = QHBoxLayout() + zl_layout.addWidget(self.zl_real_edit) + zl_layout.addWidget(QLabel('+ j')) + zl_layout.addWidget(self.zl_imag_edit) + input_layout.addWidget(zl_label, 1, 2) + input_layout.addLayout(zl_layout, 1, 3) + + # 介电常数 + er_label = QLabel('相对介电常数:') + self.er_edit = QDoubleSpinBox() + self.er_edit.setRange(1.0, 20.0) + self.er_edit.setValue(4.4) + input_layout.addWidget(er_label, 2, 0) + input_layout.addWidget(self.er_edit, 2, 1) + + # 损耗正切 + tand_label = QLabel('损耗正切:') + self.tand_edit = QDoubleSpinBox() + self.tand_edit.setRange(0.0, 0.1) + self.tand_edit.setValue(0.02) + self.tand_edit.setSingleStep(0.001) + input_layout.addWidget(tand_label, 2, 2) + input_layout.addWidget(self.tand_edit, 2, 3) + + # 导体电导率 + sigma_label = QLabel('导体电导率 (S/m):') + self.sigma_edit = QDoubleSpinBox() + self.sigma_edit.setRange(1e6, 1e8) + self.sigma_edit.setValue(5.8e7) # 铜的电导率 + self.sigma_edit.setSuffix('e6') + input_layout.addWidget(sigma_label, 3, 0) + input_layout.addWidget(self.sigma_edit, 3, 1) + + # 传输线长度 + length_label = QLabel('传输线长度:') + self.length_edit = QDoubleSpinBox() + self.length_edit.setRange(0.0, 1000.0) + self.length_edit.setValue(10.0) + self.length_unit_combo = QComboBox() + self.length_unit_combo.addItems(['mm', 'cm', 'm', 'λ']) + length_layout = QHBoxLayout() + length_layout.addWidget(self.length_edit) + length_layout.addWidget(self.length_unit_combo) + input_layout.addWidget(length_label, 3, 2) + input_layout.addLayout(length_layout, 3, 3) + + main_layout.addWidget(input_group) + + # 创建计算按钮区域 + button_layout = QHBoxLayout() + + # 计算按钮 + calc_button = QPushButton('计算传输线参数') + calc_button.setIcon(QIcon('resources/icons/calculate.png')) + calc_button.clicked.connect(self.calculate_transmission_line) + button_layout.addWidget(calc_button) + + # 阻抗匹配按钮 + match_button = QPushButton('阻抗匹配设计') + match_button.setIcon(QIcon('resources/icons/match.png')) + match_button.clicked.connect(self.design_impedance_match) + button_layout.addWidget(match_button) + + # 清除按钮 + clear_button = QPushButton('清除') + clear_button.setIcon(QIcon('resources/icons/clear.png')) + clear_button.clicked.connect(self.clear) + button_layout.addWidget(clear_button) + + main_layout.addLayout(button_layout) + + # 创建结果显示区域 + result_group = QGroupBox('计算结果') + result_layout = QVBoxLayout(result_group) + + # 创建结果表格 + self.result_table = QTableWidget() + self.result_table.setColumnCount(3) + self.result_table.setHorizontalHeaderLabels(['参数名称', '数值', '单位']) + self.result_table.horizontalHeader().setStretchLastSection(True) + result_layout.addWidget(self.result_table) + + # 创建结果详情区域 + self.result_detail = QTextEdit() + self.result_detail.setReadOnly(True) + self.result_detail.setFont(QFont('Consolas', 10)) + result_layout.addWidget(self.result_detail) + + main_layout.addWidget(result_group) + + # 创建史密斯圆图区域 + smith_group = QGroupBox('史密斯圆图') + smith_layout = QVBoxLayout(smith_group) + + # 创建史密斯圆图画布 + self.smith_figure, self.smith_ax = plt.subplots(figsize=(6, 6)) + self.smith_canvas = FigureCanvas(self.smith_figure) + smith_layout.addWidget(self.smith_canvas) + + # 史密斯圆图控制按钮 + smith_button_layout = QHBoxLayout() + + # 绘制负载阻抗按钮 + plot_load_button = QPushButton('绘制负载阻抗') + plot_load_button.clicked.connect(self.plot_load_impedance) + smith_button_layout.addWidget(plot_load_button) + + # 绘制传输线按钮 + plot_line_button = QPushButton('绘制传输线') + plot_line_button.clicked.connect(self.plot_transmission_line) + smith_button_layout.addWidget(plot_line_button) + + # 清空史密斯圆图按钮 + clear_smith_button = QPushButton('清空圆图') + clear_smith_button.clicked.connect(self.clear_smith_chart) + smith_button_layout.addWidget(clear_smith_button) + + smith_layout.addLayout(smith_button_layout) + + main_layout.addWidget(smith_group) + + # 填充剩余空间 + main_layout.addStretch() + + def update_line_params(self, line_type): + # 根据传输线类型更新默认参数 + if line_type == '微带线': + self.z0_edit.setValue(50.0) + self.er_edit.setValue(4.4) + self.tand_edit.setValue(0.02) + elif line_type == '带状线': + self.z0_edit.setValue(50.0) + self.er_edit.setValue(3.38) + self.tand_edit.setValue(0.0027) + elif line_type == '同轴线': + self.z0_edit.setValue(50.0) + self.er_edit.setValue(2.2) + self.tand_edit.setValue(0.0009) + elif line_type == '波导': + self.z0_edit.setValue(50.0) + self.er_edit.setValue(1.0) + self.tand_edit.setValue(0.0) + + def calculate_transmission_line(self): + # 计算传输线参数 + try: + # 获取输入参数 + freq = self.freq_edit.value() * 1e9 # Hz + z0 = self.z0_edit.value() # Ω + zl_real = self.zl_real_edit.value() # Ω + zl_imag = self.zl_imag_edit.value() # Ω + er = self.er_edit.value() + tand = self.tand_edit.value() + sigma = self.sigma_edit.value() * 1e6 # S/m + length = self.length_edit.value() + length_unit = self.length_unit_combo.currentText() + line_type = self.line_type_combo.currentText() + + # 计算波长 + c0 = 299792458 # 真空中的光速 + vp = c0 / np.sqrt(er) # 相速度 + lambda0 = c0 / freq # 自由空间波长 + lambda_g = vp / freq # 波导波长 + + # 转换传输线长度为米 + if length_unit == 'mm': + length_m = length / 1000 + elif length_unit == 'cm': + length_m = length / 100 + elif length_unit == 'm': + length_m = length + elif length_unit == 'λ': + length_m = length * lambda_g + else: + length_m = length / 1000 # 默认mm + + # 计算传播常数 + alpha_conductor = np.sqrt(np.pi * freq * 4e-7 / sigma) / (2 * z0) # 导体损耗 + alpha_dielectric = (2 * np.pi * freq * np.sqrt(er) / c0) * (tand / 2) # 介质损耗 + alpha = alpha_conductor + alpha_dielectric # 总衰减常数 + beta = 2 * np.pi / lambda_g # 相位常数 + gamma = alpha + 1j * beta # 传播常数 + + # 计算输入阻抗 + zl = complex(zl_real, zl_imag) + zin = z0 * (zl + z0 * np.tanh(gamma * length_m)) / (z0 + zl * np.tanh(gamma * length_m)) + + # 计算反射系数 + gamma_l = (zl - z0) / (zl + z0) + gamma_in = gamma_l * np.exp(-2 * gamma * length_m) + + # 计算VSWR + vswr = (1 + abs(gamma_l)) / (1 - abs(gamma_l)) + + # 计算损耗 + attenuation = 20 * np.log10(1 / abs(gamma_in)) # dB + + # 显示结果 + self.display_results({ + '频率': freq, + '特性阻抗': z0, + '负载阻抗': zl, + '介电常数': er, + '损耗正切': tand, + '导体电导率': sigma, + '传输线长度': length_m, + '自由空间波长': lambda0, + '波导波长': lambda_g, + '相速度': vp, + '衰减常数': alpha, + '相位常数': beta, + '传播常数': gamma, + '输入阻抗': zin, + '负载反射系数': gamma_l, + '输入反射系数': gamma_in, + 'VSWR': vswr, + '衰减': attenuation, + '传输线类型': line_type + }) + + # 保存到历史 + self.history_manager.add_history( + tool_name='传输线参数计算器', + input_data=f"频率: {freq/1e9} GHz, 特性阻抗: {z0} Ω, 负载阻抗: {zl_real}+j{zl_imag} Ω", + output_data=f"输入阻抗: {zin.real:.2f}+j{zin.imag:.2f} Ω, VSWR: {vswr:.2f}, 衰减: {attenuation:.2f} dB", + calculation_type='transmission_line' + ) + + except Exception as e: + QMessageBox.warning(self, '警告', f'计算错误: {str(e)}') + + def display_results(self, results): + # 显示计算结果到表格 + self.result_table.setRowCount(0) + self.calculation_results.append(results) + + # 添加结果到表格 + self.add_result_row('频率', results['频率']/1e9, 'GHz') + self.add_result_row('特性阻抗', results['特性阻抗'], 'Ω') + self.add_result_row('负载阻抗', f"{results['负载阻抗'].real:.2f}+j{results['负载阻抗'].imag:.2f}", 'Ω') + self.add_result_row('介电常数', results['介电常数'], '') + self.add_result_row('损耗正切', results['损耗正切'], '') + self.add_result_row('导体电导率', results['导体电导率']/1e6, 'e6 S/m') + self.add_result_row('传输线长度', results['传输线长度']*1000, 'mm') + self.add_result_row('自由空间波长', results['自由空间波长']*1e3, 'mm') + self.add_result_row('波导波长', results['波导波长']*1e3, 'mm') + self.add_result_row('相速度', results['相速度']/1e6, 'e6 m/s') + self.add_result_row('衰减常数', results['衰减常数'], 'Np/m') + self.add_result_row('相位常数', results['相位常数'], 'rad/m') + self.add_result_row('输入阻抗', f"{results['输入阻抗'].real:.2f}+j{results['输入阻抗'].imag:.2f}", 'Ω') + self.add_result_row('负载反射系数', f"{abs(results['负载反射系数']):.4f} ∠{np.angle(results['负载反射系数'], deg=True):.2f}°", '') + self.add_result_row('输入反射系数', f"{abs(results['输入反射系数']):.4f} ∠{np.angle(results['输入反射系数'], deg=True):.2f}°", '') + self.add_result_row('VSWR', results['VSWR'], '') + self.add_result_row('衰减', results['衰减'], 'dB') + self.add_result_row('传输线类型', results['传输线类型'], '') + + # 显示结果详情 + detail_text = "传输线参数计算结果:\n\n" + detail_text += f"传输线类型: {results['传输线类型']}\n" + detail_text += f"频率: {results['频率']/1e9:.2f} GHz\n" + detail_text += f"特性阻抗: {results['特性阻抗']:.2f} Ω\n" + detail_text += f"负载阻抗: {results['负载阻抗'].real:.2f} + j{results['负载阻抗'].imag:.2f} Ω\n" + detail_text += f"介电常数: {results['介电常数']:.2f}\n" + detail_text += f"损耗正切: {results['损耗正切']:.4f}\n" + detail_text += f"导体电导率: {results['导体电导率']/1e6:.2f}e6 S/m\n" + detail_text += f"传输线长度: {results['传输线长度']*1000:.2f} mm\n" + detail_text += f"自由空间波长: {results['自由空间波长']*1e3:.2f} mm\n" + detail_text += f"波导波长: {results['波导波长']*1e3:.2f} mm\n" + detail_text += f"相速度: {results['相速度']/1e6:.2f}e6 m/s\n" + detail_text += f"衰减常数: {results['衰减常数']:.4f} Np/m\n" + detail_text += f"相位常数: {results['相位常数']:.2f} rad/m\n" + detail_text += f"输入阻抗: {results['输入阻抗'].real:.2f} + j{results['输入阻抗'].imag:.2f} Ω\n" + detail_text += f"负载反射系数: {abs(results['负载反射系数']):.4f} ∠{np.angle(results['负载反射系数'], deg=True):.2f}°\n" + detail_text += f"输入反射系数: {abs(results['输入反射系数']):.4f} ∠{np.angle(results['输入反射系数'], deg=True):.2f}°\n" + detail_text += f"VSWR: {results['VSWR']:.2f}\n" + detail_text += f"衰减: {results['衰减']:.2f} dB\n" + + self.result_detail.setText(detail_text) + + def add_result_row(self, name, value, unit): + # 添加结果行到表格 + row = self.result_table.rowCount() + self.result_table.insertRow(row) + self.result_table.setItem(row, 0, QTableWidgetItem(name)) + self.result_table.setItem(row, 1, QTableWidgetItem(str(value))) + self.result_table.setItem(row, 2, QTableWidgetItem(unit)) + + def design_impedance_match(self): + # 阻抗匹配设计 + try: + # 获取输入参数 + z0 = self.z0_edit.value() # Ω + zl_real = self.zl_real_edit.value() # Ω + zl_imag = self.zl_imag_edit.value() # Ω + freq = self.freq_edit.value() * 1e9 # Hz + er = self.er_edit.value() + + # 计算负载阻抗 + zl = complex(zl_real, zl_imag) + + # 归一化负载阻抗 + z_normalized = zl / z0 + + # 计算反射系数 + gamma_l = (z_normalized - 1) / (z_normalized + 1) + + # 计算匹配所需的传输线长度和阻抗 + # 使用单节传输线匹配 + rho = abs(gamma_l) + phi = np.angle(gamma_l) + + # 计算传输线长度(波长) + if rho == 1: + QMessageBox.warning(self, '警告', '负载阻抗为无穷大或零,无法匹配') + return + + theta = (np.arccos((1 - rho**2) / (2 * rho * np.sin(phi))) - phi) / 2 + length_lambda = theta / (2 * np.pi) + + # 计算传输线特性阻抗 + z1_normalized = np.sqrt((1 + rho**2 + 2 * rho * np.cos(phi)) / (1 - rho**2)) + z1 = z1_normalized * z0 + + # 计算匹配网络 + match_network = { + '类型': '单节传输线匹配', + '传输线长度': length_lambda, + '传输线特性阻抗': z1, + '归一化负载阻抗': z_normalized, + '反射系数': gamma_l, + 'VSWR': (1 + rho) / (1 - rho) + } + + # 显示匹配结果 + self.display_match_results(match_network) + + # 保存到历史 + self.history_manager.add_history( + tool_name='传输线参数计算器', + input_data=f"特性阻抗: {z0} Ω, 负载阻抗: {zl_real}+j{zl_imag} Ω", + output_data=f"匹配传输线长度: {length_lambda:.4f}λ, 特性阻抗: {z1:.2f} Ω", + calculation_type='impedance_match' + ) + + except Exception as e: + QMessageBox.warning(self, '警告', f'阻抗匹配设计错误: {str(e)}') + + def display_match_results(self, match_network): + # 显示阻抗匹配结果 + result_text = "阻抗匹配设计结果:\n\n" + result_text += f"匹配网络类型: {match_network['类型']}\n" + result_text += f"归一化负载阻抗: {match_network['归一化负载阻抗'].real:.2f} + j{match_network['归一化负载阻抗'].imag:.2f}\n" + result_text += f"负载反射系数: {abs(match_network['反射系数']):.4f} ∠{np.angle(match_network['反射系数'], deg=True):.2f}°\n" + result_text += f"匹配前VSWR: {match_network['VSWR']:.2f}\n" + result_text += f"匹配传输线长度: {match_network['传输线长度']:.4f}λ\n" + result_text += f"匹配传输线特性阻抗: {match_network['传输线特性阻抗']:.2f} Ω\n" + result_text += "\n匹配后VSWR将接近1.0\n" + + self.result_detail.setText(result_text) + + # 在史密斯圆图上绘制匹配网络 + self.plot_smith_chart(match_network) + + def plot_smith_chart(self, match_network=None): + # 绘制史密斯圆图 + self.smith_ax.clear() + + # 绘制史密斯圆图网格 + self.draw_smith_grid() + + if match_network: + # 绘制负载阻抗 + z_normalized = match_network['归一化负载阻抗'] + self.plot_smith_point(z_normalized.real, z_normalized.imag, 'red', '负载阻抗') + + # 绘制匹配传输线 + gamma_l = match_network['反射系数'] + rho = abs(gamma_l) + phi = np.angle(gamma_l) + + # 计算传输线对应的反射系数轨迹 + theta = np.linspace(0, 2 * np.pi * match_network['传输线长度'], 100) + gamma = rho * np.exp(1j * (phi - 2 * theta)) + + # 转换为归一化阻抗 + z_real = (1 + gamma.real) / (1 - gamma.real**2 - gamma.imag**2) + z_imag = gamma.imag / (1 - gamma.real**2 - gamma.imag**2) + + # 绘制轨迹 + self.smith_ax.plot(z_real, z_imag, 'b-', linewidth=2, label='传输线轨迹') + + # 绘制匹配点(归一化阻抗为1) + self.plot_smith_point(1.0, 0.0, 'green', '匹配点') + + self.smith_ax.legend() + self.smith_canvas.draw() + + def draw_smith_grid(self): + # 绘制史密斯圆图网格 + # 绘制等电阻圆 + for r in [0.2, 0.5, 1, 2, 5]: + x = r / (1 + r) + y = 0 + radius = 1 / (1 + r) + circle = plt.Circle((x, y), radius, fill=False, color='gray', linestyle='--', linewidth=0.5) + self.smith_ax.add_patch(circle) + + # 绘制等电抗圆 + for x in [-5, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 5]: + if x == 0: + # 绘制虚轴 + self.smith_ax.axvline(x=0, color='gray', linestyle='--', linewidth=0.5) + else: + y = 1 / x + radius = abs(1 / x) + circle = plt.Circle((1, y), radius, fill=False, color='gray', linestyle='--', linewidth=0.5) + self.smith_ax.add_patch(circle) + + # 绘制单位圆 + unit_circle = plt.Circle((0, 0), 1, fill=False, color='black', linewidth=1) + self.smith_ax.add_patch(unit_circle) + + # 设置坐标轴 + self.smith_ax.set_xlim(-0.1, 2.1) + self.smith_ax.set_ylim(-1.1, 1.1) + self.smith_ax.set_aspect('equal') + self.smith_ax.set_xlabel('归一化电阻') + self.smith_ax.set_ylabel('归一化电抗') + self.smith_ax.set_title('史密斯圆图') + self.smith_ax.grid(True, linestyle='--', alpha=0.5) + + def plot_smith_point(self, r, x, color, label): + # 在史密斯圆图上绘制点 + self.smith_ax.plot(r, x, 'o', color=color, markersize=8, label=label) + self.smith_ax.text(r + 0.05, x + 0.05, f'({r:.2f}, {x:.2f})', fontsize=8) + + def plot_load_impedance(self): + # 绘制负载阻抗到史密斯圆图 + try: + z0 = self.z0_edit.value() # Ω + zl_real = self.zl_real_edit.value() # Ω + zl_imag = self.zl_imag_edit.value() # Ω + + # 归一化负载阻抗 + z_normalized = complex(zl_real, zl_imag) / z0 + + # 绘制史密斯圆图 + self.smith_ax.clear() + self.draw_smith_grid() + self.plot_smith_point(z_normalized.real, z_normalized.imag, 'red', '负载阻抗') + self.smith_ax.legend() + self.smith_canvas.draw() + + except Exception as e: + QMessageBox.warning(self, '警告', f'绘制错误: {str(e)}') + + def plot_transmission_line(self): + # 绘制传输线到史密斯圆图 + try: + z0 = self.z0_edit.value() # Ω + zl_real = self.zl_real_edit.value() # Ω + zl_imag = self.zl_imag_edit.value() # Ω + length = self.length_edit.value() + length_unit = self.length_unit_combo.currentText() + freq = self.freq_edit.value() * 1e9 # Hz + er = self.er_edit.value() + + # 计算波长 + c0 = 299792458 # 真空中的光速 + vp = c0 / np.sqrt(er) # 相速度 + lambda_g = vp / freq # 波导波长 + + # 转换传输线长度为波长 + if length_unit == 'mm': + length_m = length / 1000 + elif length_unit == 'cm': + length_m = length / 100 + elif length_unit == 'm': + length_m = length + elif length_unit == 'λ': + length_m = length * lambda_g + else: + length_m = length / 1000 # 默认mm + + length_lambda = length_m / lambda_g + + # 归一化负载阻抗 + z_normalized = complex(zl_real, zl_imag) / z0 + + # 计算反射系数 + gamma_l = (z_normalized - 1) / (z_normalized + 1) + + # 计算传输线对应的反射系数轨迹 + theta = np.linspace(0, 2 * np.pi * length_lambda, 100) + gamma = gamma_l * np.exp(-2j * theta) + + # 转换为归一化阻抗 + z_real = (1 + gamma.real) / (1 - gamma.real**2 - gamma.imag**2) + z_imag = gamma.imag / (1 - gamma.real**2 - gamma.imag**2) + + # 绘制史密斯圆图 + self.smith_ax.clear() + self.draw_smith_grid() + self.plot_smith_point(z_normalized.real, z_normalized.imag, 'red', '负载阻抗') + self.smith_ax.plot(z_real, z_imag, 'b-', linewidth=2, label='传输线轨迹') + + # 计算输入阻抗 + zin_normalized = (1 + gamma[-1]) / (1 - gamma[-1]) + self.plot_smith_point(zin_normalized.real, zin_normalized.imag, 'blue', '输入阻抗') + + self.smith_ax.legend() + self.smith_canvas.draw() + + except Exception as e: + QMessageBox.warning(self, '警告', f'绘制错误: {str(e)}') + + def clear_smith_chart(self): + # 清空史密斯圆图 + self.smith_ax.clear() + self.draw_smith_grid() + self.smith_canvas.draw() + + def clear(self): + # 清空输入和结果 + self.freq_edit.setValue(2.4) + self.z0_edit.setValue(50.0) + self.zl_real_edit.setValue(75.0) + self.zl_imag_edit.setValue(0.0) + self.er_edit.setValue(4.4) + self.tand_edit.setValue(0.02) + self.sigma_edit.setValue(5.8) + self.length_edit.setValue(10.0) + self.length_unit_combo.setCurrentText('mm') + self.result_table.setRowCount(0) + self.result_detail.clear() + self.clear_smith_chart() \ No newline at end of file diff --git a/ui/breadcrumb_bar.py b/ui/breadcrumb_bar.py new file mode 100644 index 0000000..d2cb6f5 --- /dev/null +++ b/ui/breadcrumb_bar.py @@ -0,0 +1,154 @@ +from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QLabel +from PyQt5.QtGui import QFont +from PyQt5.QtCore import Qt, pyqtSignal + +class BreadcrumbBar(QWidget): + item_clicked = pyqtSignal(str) + + def __init__(self, parent=None): + super().__init__(parent) + self.current_path = [] + self.init_ui() + + def init_ui(self): + self.layout = QHBoxLayout(self) + self.layout.setContentsMargins(10, 5, 10, 5) + self.layout.setSpacing(5) + self.layout.addStretch() + + # 添加首页项 + self.add_home_item() + + def add_home_item(self): + # 添加首页按钮 + home_btn = QPushButton("首页") + home_btn.setFont(QFont('Arial', 10)) + home_btn.setStyleSheet(''' + QPushButton { + background: transparent; + border: none; + color: #0078D7; + padding: 2px 5px; + border-radius: 3px; + } + QPushButton:hover { + background-color: rgba(0, 120, 215, 0.1); + } + ''') + home_btn.clicked.connect(lambda: self.item_clicked.emit("首页")) + self.layout.insertWidget(0, home_btn) + + def set_path(self, path): + # 设置面包屑路径 + self.clear() + self.add_home_item() + + # 添加新路径项 + for name, tool_name in path: + # 添加分隔符 + separator = QLabel(">") + separator.setFont(QFont('Arial', 10)) + separator.setStyleSheet('color: #666666;') + self.layout.insertWidget(self.layout.count() - 1, separator) + + # 添加项按钮 + item_btn = QPushButton(name) + item_btn.setFont(QFont('Arial', 10)) + item_btn.setStyleSheet(''' + QPushButton { + background: transparent; + border: none; + color: #0078D7; + padding: 2px 5px; + border-radius: 3px; + } + QPushButton:hover { + background-color: rgba(0, 120, 215, 0.1); + } + ''') + item_btn.clicked.connect(lambda checked, tn=tool_name: self.item_clicked.emit(tn)) + self.layout.insertWidget(self.layout.count() - 1, item_btn) + + # 保存到当前路径 + self.current_path.append((name, tool_name)) + + def clear(self): + # 清空面包屑(保留最后一个拉伸项) + while self.layout.count() > 1: + widget = self.layout.takeAt(0).widget() + if widget: + widget.deleteLater() + self.current_path = [] + + def set_theme(self, theme): + # 根据主题更新样式 + if theme == 'dark': + home_btn = self.layout.itemAt(0).widget() + home_btn.setStyleSheet(''' + QPushButton { + background: transparent; + border: none; + color: #1E90FF; + padding: 2px 5px; + border-radius: 3px; + } + QPushButton:hover { + background-color: rgba(30, 144, 255, 0.1); + } + ''') + + # 更新分隔符和项 + for i in range(1, self.layout.count() - 1): + widget = self.layout.itemAt(i).widget() + if isinstance(widget, QLabel): + widget.setStyleSheet('color: #AAAAAA;') + elif isinstance(widget, QPushButton): + widget.setStyleSheet(''' + QPushButton { + background: transparent; + border: none; + color: #1E90FF; + padding: 2px 5px; + border-radius: 3px; + } + QPushButton:hover { + background-color: rgba(30, 144, 255, 0.1); + } + ''') + else: + home_btn = self.layout.itemAt(0).widget() + home_btn.setStyleSheet(''' + QPushButton { + background: transparent; + border: none; + color: #0078D7; + padding: 2px 5px; + border-radius: 3px; + } + QPushButton:hover { + background-color: rgba(0, 120, 215, 0.1); + } + ''') + + # 更新分隔符和项 + for i in range(1, self.layout.count() - 1): + widget = self.layout.itemAt(i).widget() + if isinstance(widget, QLabel): + widget.setStyleSheet('color: #666666;') + elif isinstance(widget, QPushButton): + widget.setStyleSheet(''' + QPushButton { + background: transparent; + border: none; + color: #0078D7; + padding: 2px 5px; + border-radius: 3px; + } + QPushButton:hover { + background-color: rgba(0, 120, 215, 0.1); + } + ''') + + def get_current_path(self): + # 获取当前路径 + return self.current_path \ No newline at end of file diff --git a/ui/main_window.py b/ui/main_window.py new file mode 100644 index 0000000..fc9c7f1 --- /dev/null +++ b/ui/main_window.py @@ -0,0 +1,689 @@ +from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QStackedWidget, + QMenuBar, QToolBar, QAction, QStatusBar, QLabel, QSplitter, + QMessageBox, QInputDialog, QFileDialog) +from PyQt5.QtGui import QIcon, QFont, QPixmap +from PyQt5.QtCore import Qt, QSize +import os +import sys + +# 导入工具模块 +from tools.frequency_wavelength import FrequencyWavelengthTool +from tools.power_converter import PowerConverterTool +from tools.transmission_line import TransmissionLineTool +from tools.antenna_calculator import AntennaCalculatorTool +from tools.filter_designer import FilterDesignerTool +from tools.link_budget import LinkBudgetTool + +# 导入数据和知识库模块 +from data.history_manager import HistoryManager +from knowledge.knowledge_base import KnowledgeBase + +# 导入界面组件 +from ui.theme_manager import ThemeManager +from ui.navigation_bar import NavigationBar +from ui.breadcrumb_bar import BreadcrumbBar + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + + # 初始化数据和知识库 + self.history_manager = HistoryManager() + self.knowledge_base = KnowledgeBase() + + # 初始化主题管理器 + self.theme_manager = ThemeManager() + + # 初始化工具列表 + self.tools = {} + self.current_tool = None + + # 初始化界面 + self.init_ui() + + # 初始化工具 + self.init_tools() + + def init_ui(self): + # 设置窗口标题和大小 + self.setWindowTitle('射频工具箱专业版 - RF Toolbox Professional') + self.setMinimumSize(1024, 768) + self.resize(1200, 800) + + # 设置窗口图标 + if os.path.exists('icons/rf_toolbox.ico'): + self.setWindowIcon(QIcon('icons/rf_toolbox.ico')) + + # 创建中央部件 + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # 创建主布局 + main_layout = QVBoxLayout(central_widget) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + # 创建菜单栏 + self.create_menu_bar() + + # 创建工具栏 + self.create_tool_bar() + + # 创建面包屑导航 + self.breadcrumb_bar = BreadcrumbBar() + self.breadcrumb_bar.item_clicked.connect(self.on_breadcrumb_clicked) + main_layout.addWidget(self.breadcrumb_bar) + + # 创建主内容区域(导航栏 + 内容) + self.create_main_content_area(main_layout) + + # 创建状态栏 + self.create_status_bar() + + # 设置默认主题 + self.theme_manager.set_theme('system') + self.update_theme() + + # 设置默认工具 + self.show_home() + + def create_menu_bar(self): + # 创建菜单栏 + menu_bar = self.menuBar() + + # 文件菜单 + file_menu = menu_bar.addMenu('文件') + + new_project_action = QAction('新建项目', self) + new_project_action.setShortcut('Ctrl+N') + new_project_action.triggered.connect(self.new_project) + file_menu.addAction(new_project_action) + + open_project_action = QAction('打开项目', self) + open_project_action.setShortcut('Ctrl+O') + open_project_action.triggered.connect(self.open_project) + file_menu.addAction(open_project_action) + + save_project_action = QAction('保存项目', self) + save_project_action.setShortcut('Ctrl+S') + save_project_action.triggered.connect(self.save_project) + file_menu.addAction(save_project_action) + + file_menu.addSeparator() + + import_data_action = QAction('导入数据', self) + import_data_action.triggered.connect(self.import_data) + file_menu.addAction(import_data_action) + + export_data_action = QAction('导出数据', self) + export_data_action.triggered.connect(self.export_data) + file_menu.addAction(export_data_action) + + file_menu.addSeparator() + + exit_action = QAction('退出', self) + exit_action.setShortcut('Ctrl+Q') + exit_action.triggered.connect(self.close) + file_menu.addAction(exit_action) + + # 编辑菜单 + edit_menu = menu_bar.addMenu('编辑') + + undo_action = QAction('撤销', self) + undo_action.setShortcut('Ctrl+Z') + undo_action.triggered.connect(self.undo) + edit_menu.addAction(undo_action) + + redo_action = QAction('重做', self) + redo_action.setShortcut('Ctrl+Y') + redo_action.triggered.connect(self.redo) + edit_menu.addAction(redo_action) + + edit_menu.addSeparator() + + cut_action = QAction('剪切', self) + cut_action.setShortcut('Ctrl+X') + cut_action.triggered.connect(self.cut) + edit_menu.addAction(cut_action) + + copy_action = QAction('复制', self) + copy_action.setShortcut('Ctrl+C') + copy_action.triggered.connect(self.copy) + edit_menu.addAction(copy_action) + + paste_action = QAction('粘贴', self) + paste_action.setShortcut('Ctrl+V') + paste_action.triggered.connect(self.paste) + edit_menu.addAction(paste_action) + + # 视图菜单 + view_menu = menu_bar.addMenu('视图') + + # 主题子菜单 + theme_menu = view_menu.addMenu('主题') + + system_theme_action = QAction('跟随系统', self, checkable=True) + system_theme_action.triggered.connect(lambda: self.set_theme('system')) + theme_menu.addAction(system_theme_action) + + light_theme_action = QAction('浅色主题', self, checkable=True) + light_theme_action.triggered.connect(lambda: self.set_theme('light')) + theme_menu.addAction(light_theme_action) + + dark_theme_action = QAction('深色主题', self, checkable=True) + dark_theme_action.triggered.connect(lambda: self.set_theme('dark')) + theme_menu.addAction(dark_theme_action) + + # 根据当前主题设置选中状态 + current_theme = self.theme_manager.get_current_theme() + if current_theme == 'system': + system_theme_action.setChecked(True) + elif current_theme == 'light': + light_theme_action.setChecked(True) + else: + dark_theme_action.setChecked(True) + + view_menu.addSeparator() + + full_screen_action = QAction('全屏', self, checkable=True) + full_screen_action.setShortcut('F11') + full_screen_action.triggered.connect(self.toggle_full_screen) + view_menu.addAction(full_screen_action) + + # 工具菜单 + tools_menu = menu_bar.addMenu('工具') + + frequency_wavelength_action = QAction('频率-波长计算', self) + frequency_wavelength_action.triggered.connect(lambda: self.show_tool('频率-波长计算')) + tools_menu.addAction(frequency_wavelength_action) + + power_converter_action = QAction('功率单位换算', self) + power_converter_action.triggered.connect(lambda: self.show_tool('功率单位换算')) + tools_menu.addAction(power_converter_action) + + transmission_line_action = QAction('传输线参数计算', self) + transmission_line_action.triggered.connect(lambda: self.show_tool('传输线参数计算')) + tools_menu.addAction(transmission_line_action) + + antenna_calculator_action = QAction('天线参数计算', self) + antenna_calculator_action.triggered.connect(lambda: self.show_tool('天线参数计算')) + tools_menu.addAction(antenna_calculator_action) + + filter_designer_action = QAction('滤波器设计', self) + filter_designer_action.triggered.connect(lambda: self.show_tool('滤波器设计')) + tools_menu.addAction(filter_designer_action) + + link_budget_action = QAction('射频链路预算', self) + link_budget_action.triggered.connect(lambda: self.show_tool('射频链路预算')) + tools_menu.addAction(link_budget_action) + + # 帮助菜单 + help_menu = menu_bar.addMenu('帮助') + + documentation_action = QAction('文档', self) + documentation_action.triggered.connect(self.show_documentation) + help_menu.addAction(documentation_action) + + tutorials_action = QAction('教程', self) + tutorials_action.triggered.connect(self.show_tutorials) + help_menu.addAction(tutorials_action) + + help_menu.addSeparator() + + about_action = QAction('关于', self) + about_action.triggered.connect(self.show_about) + help_menu.addAction(about_action) + + def create_tool_bar(self): + # 创建工具栏 + tool_bar = self.addToolBar('工具') + tool_bar.setIconSize(QSize(24, 24)) + + # 添加常用工具按钮 + frequency_wavelength_btn = QAction(QIcon('icons/frequency_wavelength.png'), '频率-波长计算', self) + frequency_wavelength_btn.triggered.connect(lambda: self.show_tool('频率-波长计算')) + tool_bar.addAction(frequency_wavelength_btn) + + power_converter_btn = QAction(QIcon('icons/power_converter.png'), '功率单位换算', self) + power_converter_btn.triggered.connect(lambda: self.show_tool('功率单位换算')) + tool_bar.addAction(power_converter_btn) + + transmission_line_btn = QAction(QIcon('icons/transmission_line.png'), '传输线参数计算', self) + transmission_line_btn.triggered.connect(lambda: self.show_tool('传输线参数计算')) + tool_bar.addAction(transmission_line_btn) + + antenna_btn = QAction(QIcon('icons/antenna.png'), '天线参数计算', self) + antenna_btn.triggered.connect(lambda: self.show_tool('天线参数计算')) + tool_bar.addAction(antenna_btn) + + filter_btn = QAction(QIcon('icons/filter.png'), '滤波器设计', self) + filter_btn.triggered.connect(lambda: self.show_tool('滤波器设计')) + tool_bar.addAction(filter_btn) + + link_budget_btn = QAction(QIcon('icons/link_budget.png'), '射频链路预算', self) + link_budget_btn.triggered.connect(lambda: self.show_tool('射频链路预算')) + tool_bar.addAction(link_budget_btn) + + tool_bar.addSeparator() + + history_btn = QAction(QIcon('icons/history.png'), '计算历史', self) + history_btn.triggered.connect(lambda: self.show_tool('计算历史')) + tool_bar.addAction(history_btn) + + knowledge_btn = QAction(QIcon('icons/knowledge.png'), '射频知识库', self) + knowledge_btn.triggered.connect(lambda: self.show_tool('射频知识库')) + tool_bar.addAction(knowledge_btn) + + def create_main_content_area(self, main_layout): + # 创建主内容区域 + splitter = QSplitter(Qt.Horizontal) + + # 创建导航栏 + self.navigation_bar = NavigationBar() + self.navigation_bar.tool_selected.connect(self.show_tool) + self.navigation_bar.search_triggered.connect(self.on_search_triggered) + + # 创建内容堆叠窗口 + self.content_stack = QStackedWidget() + + # 添加到分隔器 + splitter.addWidget(self.navigation_bar) + splitter.addWidget(self.content_stack) + + # 设置分隔器比例 + splitter.setStretchFactor(0, 1) + splitter.setStretchFactor(1, 4) + + main_layout.addWidget(splitter) + + def create_status_bar(self): + # 创建状态栏 + status_bar = QStatusBar() + self.setStatusBar(status_bar) + + # 添加状态栏信息 + self.status_label = QLabel('就绪') + status_bar.addWidget(self.status_label) + + # 添加主题显示 + self.theme_label = QLabel('主题: ' + self.theme_manager.get_current_theme()) + status_bar.addPermanentWidget(self.theme_label) + + def init_tools(self): + # 初始化所有工具 + self.tools['频率-波长计算'] = FrequencyWavelengthTool(self.history_manager) + self.tools['功率单位换算'] = PowerConverterTool(self.history_manager) + self.tools['传输线参数计算'] = TransmissionLineTool(self.history_manager) + self.tools['天线参数计算'] = AntennaCalculatorTool(self.history_manager) + self.tools['滤波器设计'] = FilterDesignerTool(self.history_manager) + self.tools['射频链路预算'] = LinkBudgetTool(self.history_manager) + + # 添加工具到内容堆叠窗口 + for tool_name, tool_widget in self.tools.items(): + self.content_stack.addWidget(tool_widget) + + def show_home(self): + # 显示首页 + home_widget = QWidget() + home_layout = QVBoxLayout(home_widget) + + # 标题 + title_label = QLabel('欢迎使用射频工具箱专业版') + title_label.setFont(QFont('Arial', 24, QFont.Bold)) + title_label.setAlignment(Qt.AlignCenter) + home_layout.addWidget(title_label) + + home_layout.addStretch() + + # 最近使用工具 + recent_label = QLabel('最近使用工具') + recent_label.setFont(QFont('Arial', 16, QFont.Bold)) + home_layout.addWidget(recent_label) + + recent_tools = self.navigation_bar.get_recent_tools() + if recent_tools: + for tool_name in recent_tools[:3]: # 显示最近3个工具 + tool_btn = QPushButton(tool_name) + tool_btn.setFont(QFont('Arial', 12)) + tool_btn.setStyleSheet(''' + QPushButton { + background-color: #0078D7; + color: white; + border: none; + padding: 10px; + border-radius: 5px; + margin: 5px 0; + } + QPushButton:hover { + background-color: #005A9E; + } + ''') + tool_btn.clicked.connect(lambda checked, tn=tool_name: self.show_tool(tn)) + home_layout.addWidget(tool_btn) + else: + no_recent_label = QLabel('暂无最近使用工具') + no_recent_label.setFont(QFont('Arial', 12)) + no_recent_label.setStyleSheet('color: #666666;') + home_layout.addWidget(no_recent_label) + + home_layout.addStretch() + + # 添加首页到内容堆叠窗口 + if not hasattr(self, 'home_index'): + self.home_index = self.content_stack.addWidget(home_widget) + else: + # 如果已经存在,更新内容 + self.content_stack.removeWidget(self.content_stack.widget(self.home_index)) + self.home_index = self.content_stack.addWidget(home_widget) + + # 切换到首页 + self.content_stack.setCurrentIndex(self.home_index) + + # 更新面包屑 + self.breadcrumb_bar.set_path([('首页', '首页')]) + + # 更新当前工具 + self.current_tool = None + + def show_tool(self, tool_name): + # 显示指定工具 + if tool_name == '首页': + self.show_home() + return + + if tool_name in self.tools: + # 切换到对应的工具界面 + tool_widget = self.tools[tool_name] + index = self.content_stack.indexOf(tool_widget) + self.content_stack.setCurrentIndex(index) + + # 更新面包屑 + self.breadcrumb_bar.set_path([('工具', '工具'), (tool_name, tool_name)]) + + # 更新当前工具 + self.current_tool = tool_name + + # 更新状态栏 + self.status_label.setText(f'当前工具: {tool_name}') + + # 通知工具更新主题 + tool_widget.set_theme(self.theme_manager.get_current_theme()) + elif tool_name == '计算历史': + self.show_history() + elif tool_name == '射频知识库': + self.show_knowledge_base() + elif tool_name == '项目管理': + self.show_project_manager() + elif tool_name == '设置': + self.show_settings() + + def show_history(self): + # 显示计算历史 + history_widget = QWidget() + history_layout = QVBoxLayout(history_widget) + + # 标题 + history_label = QLabel('计算历史') + history_label.setFont(QFont('Arial', 18, QFont.Bold)) + history_layout.addWidget(history_label) + + # 这里可以添加历史记录的显示和搜索功能 + history_layout.addStretch() + + # 添加历史界面到内容堆叠窗口 + if not hasattr(self, 'history_index'): + self.history_index = self.content_stack.addWidget(history_widget) + else: + # 如果已经存在,更新内容 + self.content_stack.removeWidget(self.content_stack.widget(self.history_index)) + self.history_index = self.content_stack.addWidget(history_widget) + + # 切换到历史界面 + self.content_stack.setCurrentIndex(self.history_index) + + # 更新面包屑 + self.breadcrumb_bar.set_path([('历史记录', '计算历史')]) + + # 更新当前工具 + self.current_tool = '计算历史' + + def show_knowledge_base(self): + # 显示知识库 + knowledge_widget = QWidget() + knowledge_layout = QVBoxLayout(knowledge_widget) + + # 标题 + knowledge_label = QLabel('射频知识库') + knowledge_label.setFont(QFont('Arial', 18, QFont.Bold)) + knowledge_layout.addWidget(knowledge_label) + + # 这里可以添加知识库的分类和搜索功能 + knowledge_layout.addStretch() + + # 添加知识库界面到内容堆叠窗口 + if not hasattr(self, 'knowledge_index'): + self.knowledge_index = self.content_stack.addWidget(knowledge_widget) + else: + # 如果已经存在,更新内容 + self.content_stack.removeWidget(self.content_stack.widget(self.knowledge_index)) + self.knowledge_index = self.content_stack.addWidget(knowledge_widget) + + # 切换到知识库界面 + self.content_stack.setCurrentIndex(self.knowledge_index) + + # 更新面包屑 + self.breadcrumb_bar.set_path([('知识库', '射频知识库')]) + + # 更新当前工具 + self.current_tool = '射频知识库' + + def show_project_manager(self): + # 显示项目管理 + project_widget = QWidget() + project_layout = QVBoxLayout(project_widget) + + # 标题 + project_label = QLabel('项目管理') + project_label.setFont(QFont('Arial', 18, QFont.Bold)) + project_layout.addWidget(project_label) + + # 这里可以添加项目管理的功能 + project_layout.addStretch() + + # 添加项目管理界面到内容堆叠窗口 + if not hasattr(self, 'project_index'): + self.project_index = self.content_stack.addWidget(project_widget) + else: + # 如果已经存在,更新内容 + self.content_stack.removeWidget(self.content_stack.widget(self.project_index)) + self.project_index = self.content_stack.addWidget(project_widget) + + # 切换到项目管理界面 + self.content_stack.setCurrentIndex(self.project_index) + + # 更新面包屑 + self.breadcrumb_bar.set_path([('项目管理', '项目管理')]) + + # 更新当前工具 + self.current_tool = '项目管理' + + def show_settings(self): + # 显示设置 + settings_widget = QWidget() + settings_layout = QVBoxLayout(settings_widget) + + # 标题 + settings_label = QLabel('设置') + settings_label.setFont(QFont('Arial', 18, QFont.Bold)) + settings_layout.addWidget(settings_label) + + # 这里可以添加设置功能 + settings_layout.addStretch() + + # 添加设置界面到内容堆叠窗口 + if not hasattr(self, 'settings_index'): + self.settings_index = self.content_stack.addWidget(settings_widget) + else: + # 如果已经存在,更新内容 + self.content_stack.removeWidget(self.content_stack.widget(self.settings_index)) + self.settings_index = self.content_stack.addWidget(settings_widget) + + # 切换到设置界面 + self.content_stack.setCurrentIndex(self.settings_index) + + # 更新面包屑 + self.breadcrumb_bar.set_path([('设置', '设置')]) + + # 更新当前工具 + self.current_tool = '设置' + + def on_breadcrumb_clicked(self, tool_name): + # 面包屑点击事件 + self.show_tool(tool_name) + + def on_search_triggered(self, keyword): + # 搜索触发事件 + QMessageBox.information(self, '搜索结果', f'搜索: {keyword}') + + def set_theme(self, theme): + # 设置主题 + self.theme_manager.set_theme(theme) + self.update_theme() + + # 更新状态栏主题显示 + self.theme_label.setText('主题: ' + self.theme_manager.get_current_theme()) + + def update_theme(self): + # 更新界面主题 + theme = self.theme_manager.get_current_theme() + + # 更新导航栏主题 + self.navigation_bar.set_theme(theme) + + # 更新面包屑主题 + self.breadcrumb_bar.set_theme(theme) + + # 更新所有工具的主题 + for tool_widget in self.tools.values(): + tool_widget.set_theme(theme) + + # 更新主窗口样式 + if theme == 'dark': + self.setStyleSheet(''' + QMainWindow { background-color: #1E1E1E; } + QMenuBar { background-color: #2D2D30; color: #FFFFFF; } + QMenuBar::item { background-color: #2D2D30; color: #FFFFFF; padding: 5px 10px; } + QMenuBar::item:selected { background-color: #3E3E42; } + QMenu { background-color: #2D2D30; color: #FFFFFF; } + QMenu::item { background-color: #2D2D30; color: #FFFFFF; padding: 5px 20px; } + QMenu::item:selected { background-color: #1E90FF; } + QToolBar { background-color: #2D2D30; } + QStatusBar { background-color: #2D2D30; color: #FFFFFF; } + ''') + else: + self.setStyleSheet(''' + QMainWindow { background-color: #F0F0F0; } + QMenuBar { background-color: #EDEDED; color: #000000; } + QMenuBar::item { background-color: #EDEDED; color: #000000; padding: 5px 10px; } + QMenuBar::item:selected { background-color: #0078D7; color: #FFFFFF; } + QMenu { background-color: #FFFFFF; color: #000000; } + QMenu::item { background-color: #FFFFFF; color: #000000; padding: 5px 20px; } + QMenu::item:selected { background-color: #0078D7; color: #FFFFFF; } + QToolBar { background-color: #EDEDED; } + QStatusBar { background-color: #EDEDED; color: #000000; } + ''') + + def toggle_full_screen(self): + # 切换全屏 + if self.isFullScreen(): + self.showNormal() + else: + self.showFullScreen() + + def new_project(self): + # 新建项目 + project_name, ok = QInputDialog.getText(self, '新建项目', '项目名称:') + if ok and project_name: + self.history_manager.add_project(project_name) + QMessageBox.information(self, '新建项目', f'项目 "{project_name}" 已创建') + + def open_project(self): + # 打开项目 + projects = self.history_manager.get_projects() + if not projects: + QMessageBox.information(self, '打开项目', '暂无项目') + return + + project_names = [p['name'] for p in projects] + project_name, ok = QInputDialog.getItem(self, '打开项目', '选择项目:', project_names, 0, False) + if ok and project_name: + QMessageBox.information(self, '打开项目', f'项目 "{project_name}" 已打开') + + def save_project(self): + # 保存项目 + if self.current_tool: + QMessageBox.information(self, '保存项目', f'当前工具 "{self.current_tool}" 的数据已保存') + else: + QMessageBox.information(self, '保存项目', '请先选择一个工具') + + def import_data(self): + # 导入数据 + file_path, _ = QFileDialog.getOpenFileName(self, '导入数据', '', 'JSON Files (*.json);;CSV Files (*.csv);;All Files (*.*)') + if file_path: + QMessageBox.information(self, '导入数据', f'数据已从 "{file_path}" 导入') + + def export_data(self): + # 导出数据 + if self.current_tool: + file_path, _ = QFileDialog.getSaveFileName(self, '导出数据', '', 'JSON Files (*.json);;CSV Files (*.csv);;Text Files (*.txt)') + if file_path: + QMessageBox.information(self, '导出数据', f'数据已导出到 "{file_path}"') + else: + QMessageBox.information(self, '导出数据', '请先选择一个工具') + + def undo(self): + # 撤销操作 + if self.current_tool and self.current_tool in self.tools: + self.tools[self.current_tool].undo() + + def redo(self): + # 重做操作 + if self.current_tool and self.current_tool in self.tools: + self.tools[self.current_tool].redo() + + def cut(self): + # 剪切操作 + if self.current_tool and self.current_tool in self.tools: + self.tools[self.current_tool].cut() + + def copy(self): + # 复制操作 + if self.current_tool and self.current_tool in self.tools: + self.tools[self.current_tool].copy() + + def paste(self): + # 粘贴操作 + if self.current_tool and self.current_tool in self.tools: + self.tools[self.current_tool].paste() + + def show_documentation(self): + # 显示文档 + QMessageBox.information(self, '文档', '射频工具箱专业版使用文档') + + def show_tutorials(self): + # 显示教程 + QMessageBox.information(self, '教程', '射频工具箱专业版教程') + + def show_about(self): + # 显示关于 + about_text = ''' + 射频工具箱专业版 + RF Toolbox Professional + + 版本: 2.0 + 作者: 射频工程团队 + + 一个功能强大的射频工程设计工具 + 包含频率-波长计算、功率转换、传输线计算、天线设计、滤波器设计和链路预算等功能 + ''' + QMessageBox.about(self, '关于', about_text) \ No newline at end of file diff --git a/ui/navigation_bar.py b/ui/navigation_bar.py new file mode 100644 index 0000000..25b505b --- /dev/null +++ b/ui/navigation_bar.py @@ -0,0 +1,223 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QListWidget, QListWidgetItem, + QLabel, QPushButton, QHBoxLayout, QMenu, QAction, + QLineEdit, QCompleter) +from PyQt5.QtGui import QIcon, QFont, QBrush, QColor +from PyQt5.QtCore import Qt, pyqtSignal +import os + +class NavigationBar(QWidget): + tool_selected = pyqtSignal(str) + category_selected = pyqtSignal(str) + search_triggered = pyqtSignal(str) + + def __init__(self, parent=None): + super().__init__(parent) + self.current_category = None + self.favorites = self.load_favorites() + self.recent_tools = self.load_recent_tools() + self.init_ui() + + def init_ui(self): + self.setMinimumWidth(250) + self.setMaximumWidth(350) + + # 主布局 + layout = QVBoxLayout(self) + layout.setContentsMargins(10, 10, 10, 10) + layout.setSpacing(10) + + # 搜索框 + self.search_edit = QLineEdit() + self.search_edit.setPlaceholderText('搜索工具或知识库...') + self.search_edit.setFont(QFont('Consolas', 10)) + self.search_edit.setStyleSheet('padding: 8px; border-radius: 5px; border: 1px solid #ccc;') + self.search_edit.textChanged.connect(self.on_search_text_changed) + layout.addWidget(self.search_edit) + + # 收藏夹 + favorites_label = QLabel('收藏夹') + favorites_label.setFont(QFont('Arial', 12, QFont.Bold)) + layout.addWidget(favorites_label) + + self.favorites_list = QListWidget() + self.favorites_list.itemClicked.connect(self.on_favorite_clicked) + self.favorites_list.setStyleSheet('border: 1px solid #ccc; border-radius: 5px;') + self.update_favorites_list() + layout.addWidget(self.favorites_list) + + # 最近使用 + recent_label = QLabel('最近使用') + recent_label.setFont(QFont('Arial', 12, QFont.Bold)) + layout.addWidget(recent_label) + + self.recent_list = QListWidget() + self.recent_list.itemClicked.connect(self.on_recent_clicked) + self.recent_list.setStyleSheet('border: 1px solid #ccc; border-radius: 5px;') + self.update_recent_list() + layout.addWidget(self.recent_list) + + # 工具分类 + category_label = QLabel('工具分类') + category_label.setFont(QFont('Arial', 12, QFont.Bold)) + layout.addWidget(category_label) + + self.category_list = QListWidget() + self.category_list.itemClicked.connect(self.on_category_clicked) + self.category_list.setStyleSheet('border: 1px solid #ccc; border-radius: 5px;') + + # 添加分类 + categories = [ + ('频率与波长', 'frequency_wavelength.png', '频率-波长计算'), + ('功率转换', 'power_converter.png', '功率单位换算'), + ('传输线', 'transmission_line.png', '传输线参数计算'), + ('天线', 'antenna.png', '天线参数计算'), + ('滤波器', 'filter.png', '滤波器设计'), + ('链路预算', 'link_budget.png', '射频链路预算'), + ('知识库', 'knowledge.png', '射频知识库'), + ('历史记录', 'history.png', '计算历史'), + ('项目管理', 'project.png', '项目管理'), + ('设置', 'settings.png', '设置') + ] + + for category_name, icon_name, tool_name in categories: + item = QListWidgetItem(QIcon(f'icons/{icon_name}'), category_name) + item.setData(Qt.UserRole, tool_name) + self.category_list.addItem(item) + + layout.addWidget(self.category_list) + + # 填充空间 + layout.addStretch() + + def on_search_text_changed(self, text): + if len(text) > 2: + self.search_triggered.emit(text) + + def on_category_clicked(self, item): + tool_name = item.data(Qt.UserRole) + self.tool_selected.emit(tool_name) + + # 更新最近使用 + self.add_to_recent(tool_name) + + def on_favorite_clicked(self, item): + tool_name = item.data(Qt.UserRole) + self.tool_selected.emit(tool_name) + + def on_recent_clicked(self, item): + tool_name = item.data(Qt.UserRole) + self.tool_selected.emit(tool_name) + + # 重新添加到最近使用(更新顺序) + self.add_to_recent(tool_name) + + def add_to_recent(self, tool_name): + # 添加到最近使用 + if tool_name in self.recent_tools: + self.recent_tools.remove(tool_name) + + self.recent_tools.insert(0, tool_name) + + # 限制最近使用工具的数量 + if len(self.recent_tools) > 10: + self.recent_tools = self.recent_tools[:10] + + self.save_recent_tools() + self.update_recent_list() + + def update_recent_list(self): + # 更新最近使用列表 + self.recent_list.clear() + + for tool_name in self.recent_tools: + item = QListWidgetItem(QIcon('icons/recent.png'), tool_name) + item.setData(Qt.UserRole, tool_name) + self.recent_list.addItem(item) + + def toggle_favorite(self, tool_name): + # 切换收藏状态 + if tool_name in self.favorites: + self.favorites.remove(tool_name) + else: + self.favorites.append(tool_name) + + self.save_favorites() + self.update_favorites_list() + + def is_favorite(self, tool_name): + # 检查是否为收藏 + return tool_name in self.favorites + + def update_favorites_list(self): + # 更新收藏夹列表 + self.favorites_list.clear() + + for tool_name in self.favorites: + item = QListWidgetItem(QIcon('icons/favorite.png'), tool_name) + item.setData(Qt.UserRole, tool_name) + self.favorites_list.addItem(item) + + def load_favorites(self): + # 加载收藏夹 + try: + if os.path.exists('data/favorites.json'): + with open('data/favorites.json', 'r') as f: + return json.load(f) + except: + pass + + return [] + + def save_favorites(self): + # 保存收藏夹 + os.makedirs('data', exist_ok=True) + + try: + with open('data/favorites.json', 'w') as f: + json.dump(self.favorites, f) + except: + pass + + def load_recent_tools(self): + # 加载最近使用工具 + try: + if os.path.exists('data/recent.json'): + with open('data/recent.json', 'r') as f: + return json.load(f) + except: + pass + + return [] + + def save_recent_tools(self): + # 保存最近使用工具 + os.makedirs('data', exist_ok=True) + + try: + with open('data/recent.json', 'w') as f: + json.dump(self.recent_tools, f) + except: + pass + + def set_theme(self, theme): + # 设置主题 + if theme == 'dark': + self.setStyleSheet(''' + QWidget { background-color: #1E1E1E; color: #FFFFFF; } + QListWidget { background-color: #2D2D30; border: 1px solid #555555; border-radius: 5px; } + QListWidget::item { padding: 8px; border-bottom: 1px solid #555555; } + QListWidget::item:hover { background-color: #3E3E42; } + QListWidget::item:selected { background-color: #1E90FF; } + QLineEdit { background-color: #2D2D30; color: #FFFFFF; border: 1px solid #555555; padding: 8px; border-radius: 5px; } + QLabel { color: #FFFFFF; } + ''') + else: + self.setStyleSheet(''' + QWidget { background-color: #F0F0F0; color: #000000; } + QListWidget { background-color: #FFFFFF; border: 1px solid #CCCCCC; border-radius: 5px; } + QListWidget::item { padding: 8px; border-bottom: 1px solid #EDEDED; } + QListWidget::item:hover { background-color: #EDEDED; } + QListWidget::item:selected { background-color: #0078D7; color: #FFFFFF; } + QLineEdit { background-color: #FFFFFF; color: #000000; border: 1px solid #CCCCCC; padding: 8px; border-radius: 5px; } + QLabel { color: #000000; } + ''') \ No newline at end of file diff --git a/ui/theme_manager.py b/ui/theme_manager.py new file mode 100644 index 0000000..c8ce8a1 --- /dev/null +++ b/ui/theme_manager.py @@ -0,0 +1,145 @@ +from PyQt5.QtGui import QColor, QPalette +from PyQt5.QtWidgets import QApplication + +class ThemeManager: + def __init__(self): + self.current_theme = 'system' # 'light', 'dark', or 'system' + self.light_theme = self._create_light_theme() + self.dark_theme = self._create_dark_theme() + self.system_theme = self._detect_system_theme() + + def _create_light_theme(self): + # 创建浅色主题 + palette = QPalette() + + # 背景色 + palette.setColor(QPalette.Window, QColor(240, 240, 240)) + palette.setColor(QPalette.WindowText, QColor(0, 0, 0)) + + # 基础颜色 + palette.setColor(QPalette.Base, QColor(255, 255, 255)) + palette.setColor(QPalette.AlternateBase, QColor(245, 245, 245)) + palette.setColor(QPalette.ToolTipBase, QColor(255, 255, 220)) + palette.setColor(QPalette.ToolTipText, QColor(0, 0, 0)) + + # 文本颜色 + palette.setColor(QPalette.Text, QColor(0, 0, 0)) + palette.setColor(QPalette.ButtonText, QColor(0, 0, 0)) + palette.setColor(QPalette.Link, QColor(0, 100, 200)) + + # 按钮颜色 + palette.setColor(QPalette.Button, QColor(240, 240, 240)) + palette.setColor(QPalette.Highlight, QColor(0, 120, 215)) + palette.setColor(QPalette.HighlightedText, QColor(255, 255, 255)) + + # 禁用颜色 + palette.setColor(QPalette.Disabled, QPalette.Text, QColor(127, 127, 127)) + palette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(127, 127, 127)) + palette.setColor(QPalette.Disabled, QPalette.Highlight, QColor(180, 180, 180)) + + return palette + + def _create_dark_theme(self): + # 创建深色主题 + palette = QPalette() + + # 背景色 + palette.setColor(QPalette.Window, QColor(30, 30, 30)) + palette.setColor(QPalette.WindowText, QColor(255, 255, 255)) + + # 基础颜色 + palette.setColor(QPalette.Base, QColor(40, 40, 40)) + palette.setColor(QPalette.AlternateBase, QColor(50, 50, 50)) + palette.setColor(QPalette.ToolTipBase, QColor(60, 60, 60)) + palette.setColor(QPalette.ToolTipText, QColor(255, 255, 255)) + + # 文本颜色 + palette.setColor(QPalette.Text, QColor(255, 255, 255)) + palette.setColor(QPalette.ButtonText, QColor(255, 255, 255)) + palette.setColor(QPalette.Link, QColor(100, 180, 255)) + + # 按钮颜色 + palette.setColor(QPalette.Button, QColor(50, 50, 50)) + palette.setColor(QPalette.Highlight, QColor(100, 180, 255)) + palette.setColor(QPalette.HighlightedText, QColor(0, 0, 0)) + + # 禁用颜色 + palette.setColor(QPalette.Disabled, QPalette.Text, QColor(100, 100, 100)) + palette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(100, 100, 100)) + palette.setColor(QPalette.Disabled, QPalette.Highlight, QColor(80, 80, 80)) + + return palette + + def _detect_system_theme(self): + # 检测系统主题 + if hasattr(QApplication, 'styleHints') and hasattr(QApplication.styleHints(), 'colorScheme'): + # Qt 6.5+ 支持系统主题检测 + color_scheme = QApplication.styleHints().colorScheme() + return 'dark' if color_scheme == QApplication.Dark else 'light' + else: + # 对于旧版本Qt,默认返回浅色主题 + return 'light' + + def set_theme(self, theme): + # 设置主题 + self.current_theme = theme + + if theme == 'system': + detected_theme = self._detect_system_theme() + palette = self.dark_theme if detected_theme == 'dark' else self.light_theme + elif theme == 'dark': + palette = self.dark_theme + else: + palette = self.light_theme + + QApplication.instance().setPalette(palette) + + def get_current_theme(self): + # 获取当前主题 + return self.current_theme + + def is_dark_theme(self): + # 检查是否为深色主题 + if self.current_theme == 'system': + return self._detect_system_theme() == 'dark' + return self.current_theme == 'dark' + + def get_theme_colors(self): + # 获取当前主题的颜色 + if self.current_theme == 'system': + detected_theme = self._detect_system_theme() + return self._get_dark_theme_colors() if detected_theme == 'dark' else self._get_light_theme_colors() + elif self.current_theme == 'dark': + return self._get_dark_theme_colors() + else: + return self._get_light_theme_colors() + + def _get_light_theme_colors(self): + # 获取浅色主题的颜色 + return { + 'background': '#F0F0F0', + 'window': '#FFFFFF', + 'text': '#000000', + 'accent': '#0078D7', + 'secondary': '#EDEDED', + 'border': '#CCCCCC', + 'success': '#00B050', + 'warning': '#FFC107', + 'error': '#FF0000', + 'info': '#17A2B8' + } + + def _get_dark_theme_colors(self): + # 获取深色主题的颜色 + return { + 'background': '#1E1E1E', + 'window': '#2D2D30', + 'text': '#FFFFFF', + 'accent': '#1E90FF', + 'secondary': '#3E3E42', + 'border': '#555555', + 'success': '#00B050', + 'warning': '#FFC107', + 'error': '#FF0000', + 'info': '#17A2B8' + } \ No newline at end of file diff --git a/utils/visualization.py b/utils/visualization.py new file mode 100644 index 0000000..a94ad20 --- /dev/null +++ b/utils/visualization.py @@ -0,0 +1,288 @@ +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.figure import Figure +from PyQt5 import QtWidgets + +class PlotCanvas(FigureCanvas): + def __init__(self, parent=None, width=5, height=4, dpi=100): + self.fig = Figure(figsize=(width, height), dpi=dpi) + self.axes = self.fig.add_subplot(111) + super().__init__(self.fig) + self.setParent(parent) + FigureCanvas.setSizePolicy(self, + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Expanding) + FigureCanvas.updateGeometry(self) + +def plot_wavelength_distribution(frequencies, wavelengths,介质=None): + """ + 绘制频率与波长的关系图 + + 参数: + frequencies: 频率数组 (Hz) + wavelengths: 波长数组 (m) + 介质: 介质名称 (可选) + + 返回: + PlotCanvas: 包含图表的Qt部件 + """ + canvas = PlotCanvas(width=8, height=6, dpi=100) + + # 清除之前的绘图 + canvas.axes.clear() + + # 绘制频率与波长的关系 + canvas.axes.plot(frequencies, wavelengths, 'b-', linewidth=2, label='波长') + + # 设置坐标轴标签 + canvas.axes.set_xlabel('频率 (Hz)', fontsize=12) + canvas.axes.set_ylabel('波长 (m)', fontsize=12) + + # 设置标题 + if 介质: + canvas.axes.set_title(f'频率与波长的关系 ({介质})', fontsize=14, fontweight='bold') + else: + canvas.axes.set_title('频率与波长的关系', fontsize=14, fontweight='bold') + + # 添加网格 + canvas.axes.grid(True, linestyle='--', alpha=0.7) + + # 添加图例 + canvas.axes.legend() + + # 自动调整布局 + canvas.fig.tight_layout() + + return canvas + +def plot_power_conversion(power_values, unit_from, unit_to): + """ + 绘制功率转换结果图 + + 参数: + power_values: 功率值数组 + unit_from: 原始单位 + unit_to: 目标单位 + + 返回: + PlotCanvas: 包含图表的Qt部件 + """ + canvas = PlotCanvas(width=8, height=6, dpi=100) + + # 清除之前的绘图 + canvas.axes.clear() + + # 创建x轴数据 (样本索引) + x = np.arange(len(power_values)) + + # 绘制功率转换结果 + canvas.axes.plot(x, power_values, 'r-', linewidth=2, marker='o', markersize=5) + + # 设置坐标轴标签 + canvas.axes.set_xlabel('样本', fontsize=12) + canvas.axes.set_ylabel(f'功率 ({unit_to})', fontsize=12) + + # 设置标题 + canvas.axes.set_title(f'功率转换结果: {unit_from} 到 {unit_to}', fontsize=14, fontweight='bold') + + # 添加网格 + canvas.axes.grid(True, linestyle='--', alpha=0.7) + + # 自动调整布局 + canvas.fig.tight_layout() + + return canvas + +def plot_transmission_line_parameters(frequency, parameter_values, parameter_name, unit): + """ + 绘制传输线参数随频率变化的关系图 + + 参数: + frequency: 频率数组 (Hz) + parameter_values: 参数值数组 + parameter_name: 参数名称 (如"特性阻抗", "衰减常数") + unit: 参数单位 + + 返回: + PlotCanvas: 包含图表的Qt部件 + """ + canvas = PlotCanvas(width=8, height=6, dpi=100) + + # 清除之前的绘图 + canvas.axes.clear() + + # 绘制参数随频率变化的关系 + canvas.axes.plot(frequency, parameter_values, 'g-', linewidth=2) + + # 设置坐标轴标签 + canvas.axes.set_xlabel('频率 (Hz)', fontsize=12) + canvas.axes.set_ylabel(f'{parameter_name} ({unit})', fontsize=12) + + # 设置标题 + canvas.axes.set_title(f'{parameter_name} 随频率变化的关系', fontsize=14, fontweight='bold') + + # 添加网格 + canvas.axes.grid(True, linestyle='--', alpha=0.7) + + # 自动调整布局 + canvas.fig.tight_layout() + + return canvas + +def plot_antenna_radiation_pattern(theta, phi, gain, antenna_type): + """ + 绘制天线辐射方向图 + + 参数: + theta: 方位角数组 + phi: 仰角数组 + gain: 增益数组 + antenna_type: 天线类型 + + 返回: + PlotCanvas: 包含图表的Qt部件 + """ + canvas = PlotCanvas(width=8, height=6, dpi=100) + + # 清除之前的绘图 + canvas.axes.clear() + + # 创建极坐标图 + ax = canvas.fig.add_subplot(111, projection='polar') + + # 绘制辐射方向图 + ax.plot(theta, gain, 'b-', linewidth=2) + + # 设置标题 + ax.set_title(f'{antenna_type} 辐射方向图', fontsize=14, fontweight='bold', y=1.1) + + # 添加网格 + ax.grid(True, linestyle='--', alpha=0.7) + + # 自动调整布局 + canvas.fig.tight_layout() + + return canvas + +def plot_filter_response(frequency, magnitude, phase, filter_type): + """ + 绘制滤波器的幅频响应和相频响应 + + 参数: + frequency: 频率数组 (Hz) + magnitude: 幅度响应数组 (dB) + phase: 相位响应数组 (度) + filter_type: 滤波器类型 + + 返回: + PlotCanvas: 包含图表的Qt部件 + """ + canvas = PlotCanvas(width=10, height=6, dpi=100) + + # 清除之前的绘图 + canvas.axes.clear() + + # 创建上下两个子图 + ax1 = canvas.fig.add_subplot(211) + ax2 = canvas.fig.add_subplot(212) + + # 绘制幅频响应 + ax1.plot(frequency, magnitude, 'b-', linewidth=2) + ax1.set_title(f'{filter_type} 滤波器响应', fontsize=14, fontweight='bold') + ax1.set_ylabel('幅度 (dB)', fontsize=12) + ax1.grid(True, linestyle='--', alpha=0.7) + + # 绘制相频响应 + ax2.plot(frequency, phase, 'r-', linewidth=2) + ax2.set_xlabel('频率 (Hz)', fontsize=12) + ax2.set_ylabel('相位 (度)', fontsize=12) + ax2.grid(True, linestyle='--', alpha=0.7) + + # 自动调整布局 + canvas.fig.tight_layout() + + return canvas + +def plot_link_budget(components, gains, losses): + """ + 绘制链路预算的增益和损耗分布 + + 参数: + components: 组件名称数组 + gains: 增益数组 (dB) + losses: 损耗数组 (dB) + + 返回: + PlotCanvas: 包含图表的Qt部件 + """ + canvas = PlotCanvas(width=10, height=6, dpi=100) + + # 清除之前的绘图 + canvas.axes.clear() + + # 创建x轴数据 (组件索引) + x = np.arange(len(components)) + + # 绘制增益和损耗 + canvas.axes.bar(x - 0.2, gains, 0.4, label='增益 (dB)', color='g') + canvas.axes.bar(x + 0.2, losses, 0.4, label='损耗 (dB)', color='r') + + # 设置坐标轴标签 + canvas.axes.set_xlabel('组件', fontsize=12) + canvas.axes.set_ylabel('值 (dB)', fontsize=12) + + # 设置标题 + canvas.axes.set_title('链路预算分布', fontsize=14, fontweight='bold') + + # 设置x轴刻度标签 + canvas.axes.set_xticks(x) + canvas.axes.set_xticklabels(components, rotation=45, ha='right') + + # 添加网格 + canvas.axes.grid(True, linestyle='--', alpha=0.7, axis='y') + + # 添加图例 + canvas.axes.legend() + + # 自动调整布局 + canvas.fig.tight_layout() + + return canvas + +def plot_smith_chart(reflection_coefficient): + """ + 绘制史密斯圆图 + + 参数: + reflection_coefficient: 反射系数数组 + + 返回: + PlotCanvas: 包含图表的Qt部件 + """ + canvas = PlotCanvas(width=8, height=6, dpi=100) + + # 清除之前的绘图 + canvas.axes.clear() + + # 创建史密斯圆图 + ax = canvas.fig.add_subplot(111, projection='polar') + + # 绘制单位圆 + theta = np.linspace(0, 2*np.pi, 100) + ax.plot(theta, np.ones_like(theta), 'k-', linewidth=2) + + # 绘制反射系数点 + for gamma in reflection_coefficient: + ax.plot(np.angle(gamma), np.abs(gamma), 'ro', markersize=5) + + # 设置标题 + ax.set_title('史密斯圆图', fontsize=14, fontweight='bold', y=1.1) + + # 添加网格 + ax.grid(True, linestyle='--', alpha=0.7) + + # 自动调整布局 + canvas.fig.tight_layout() + + return canvas \ No newline at end of file