- 积分
- 4888
- 明经币
- 个
- 注册时间
- 2022-9-23
- 在线时间
- 小时
- 威望
-
- 金钱
- 个
- 贡献
-
- 激情
-
|
# ===== 工程计算工具箱.py =====
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import math
import csv
import random
# === 工具类 ===
class Utils:
@staticmethod
def validate_float(value_str: str, field_name: str) -> float:
"""验证浮点数输入"""
if not value_str:
raise ValueError(f"{field_name}不能为空")
try:
return float(value_str)
except ValueError:
raise ValueError(f"{field_name}必须是有效的数字")
# === 桥梁计算 ===
class BridgeCalculatorApp:
def __init__(self, parent):
self.parent = parent
self.setup_ui()
def setup_ui(self):
# 主框架
main_frame = ttk.Frame(self.parent)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 左侧输入区域
input_frame = ttk.Frame(main_frame)
input_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)
# 创建四个计算模块的输入部分
self.setup_structure_positioning_input(input_frame)
self.setup_intersection_input(input_frame)
self.setup_padstone_input(input_frame)
self.setup_column_width_input(input_frame)
# 右侧结果区域
result_frame = ttk.Frame(main_frame)
result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
# 创建结果展示区域
self.setup_result_area(result_frame)
def setup_structure_positioning_input(self, parent):
"""结构定位计算输入区域"""
frame = ttk.LabelFrame(parent, text="结构定位计算", padding=10)
frame.pack(fill=tk.X, pady=5)
# 输入类型选择
self.struct_input_type = tk.StringVar(value="两点")
type_frame = ttk.Frame(frame)
type_frame.pack(fill=tk.X, pady=2)
ttk.Label(type_frame, text="输入类型:").pack(side=tk.LEFT)
ttk.Combobox(type_frame, textvariable=self.struct_input_type,
values=["两点", "四点"], state="readonly",
width=8).pack(side=tk.LEFT, padx=5)
self.struct_input_type.trace_add("write", self.update_struct_input_fields)
# 动态输入区域
self.struct_input_frame = ttk.Frame(frame)
self.struct_input_frame.pack(fill=tk.X, pady=2)
# 参数输入
param_frame = ttk.Frame(frame)
param_frame.pack(fill=tk.X, pady=2)
ttk.Label(param_frame, text="长(m):").pack(side=tk.LEFT)
self.length_entry = ttk.Entry(param_frame, width=10)
self.length_entry.pack(side=tk.LEFT, padx=2)
ttk.Label(param_frame, text="宽(m):").pack(side=tk.LEFT)
self.width_entry = ttk.Entry(param_frame, width=10)
self.width_entry.pack(side=tk.LEFT, padx=2)
# 按钮区域
btn_frame = ttk.Frame(frame)
btn_frame.pack(fill=tk.X, pady=2)
ttk.Button(btn_frame, text="加载CSV", command=self.load_struct_csv).pack(side=tk.LEFT, padx=2)
ttk.Button(btn_frame, text="计算", command=self.calculate_structure).pack(side=tk.LEFT, padx=2)
self.struct_entries = []
self.update_struct_input_fields()
def update_struct_input_fields(self, *args):
"""更新结构定位输入字段"""
for widget in self.struct_input_frame.winfo_children():
widget.destroy()
self.struct_entries.clear()
required_points = 2 if self.struct_input_type.get() == "两点" else 4
for i in range(required_points):
row = ttk.Frame(self.struct_input_frame)
row.pack(fill=tk.X, pady=1)
ttk.Label(row, text=f"点{i+1} X:").pack(side=tk.LEFT)
x_entry = ttk.Entry(row, width=10)
x_entry.pack(side=tk.LEFT, padx=2)
ttk.Label(row, text="Y:").pack(side=tk.LEFT)
y_entry = ttk.Entry(row, width=10)
y_entry.pack(side=tk.LEFT, padx=2)
self.struct_entries.append((x_entry, y_entry))
def load_struct_csv(self):
"""加载结构定位CSV文件"""
file_path = filedialog.askopenfilename(filetypes=[("CSV文件", "*.csv")])
if not file_path:
return
try:
with open(file_path, newline='') as f:
reader = csv.reader(f)
points = [tuple(map(float, row)) for row in reader if len(row) >= 2]
required_points = 2 if self.struct_input_type.get() == "两点" else 4
if len(points) < required_points:
raise ValueError(f"需要至少{required_points}个有效坐标点")
for i, (x, y) in enumerate(points[:required_points]):
self.struct_entries[i][0].delete(0, tk.END)
self.struct_entries[i][0].insert(0, str(x))
self.struct_entries[i][1].delete(0, tk.END)
self.struct_entries[i][1].insert(0, str(y))
except Exception as e:
messagebox.showerror("文件错误", str(e))
def calculate_structure(self):
"""执行结构定位计算"""
try:
points = [(float(x.get()), float(y.get()))
for x, y in self.struct_entries]
length = float(self.length_entry.get())
width = float(self.width_entry.get())
if len(points) == 2:
result = self.calculate_two_points(points, length, width)
else:
result = self.calculate_four_points(points, length, width)
self.show_result(result)
except Exception as e:
messagebox.showerror("计算错误", str(e))
def calculate_two_points(self, points, length, width):
"""两点计算模式"""
(x1, y1), (x2, y2) = points
dx, dy = x2 - x1, y2 - y1
current_length = math.hypot(dx, dy)
if current_length == 0:
raise ValueError("输入点重合")
if length < current_length:
raise ValueError("结构长度不能小于基准点间距")
extension = (length - current_length) / 2
dir_x, dir_y = dx / current_length, dy / current_length
new_p1 = (x1 - extension * dir_x, y1 - extension * dir_y)
new_p2 = (x2 + extension * dir_x, y2 + extension * dir_y)
perp_x, perp_y = -dir_y * width / 2, dir_x * width / 2
result = [
(new_p1[0] + perp_x, new_p1[1] + perp_y),
(new_p2[0] + perp_x, new_p2[1] + perp_y),
(new_p2[0] - perp_x, new_p2[1] - perp_y),
(new_p1[0] - perp_x, new_p1[1] - perp_y)
]
return "\n".join([f"点{i+1}: ({x:.3f}, {y:.3f})" for i, (x, y) in enumerate(result)])
def calculate_four_points(self, points, length, width):
"""四点计算模式"""
mid1 = ((points[0][0] + points[1][0]) / 2, (points[0][1] + points[1][1]) / 2)
mid2 = ((points[2][0] + points[3][0]) / 2, (points[2][1] + points[3][1]) / 2)
return self.calculate_two_points([mid1, mid2], length, width)
def setup_intersection_input(self, parent):
"""回头曲线交点分解输入区域"""
frame = ttk.LabelFrame(parent, text="回头曲线交点分解", padding=10)
frame.pack(fill=tk.X, pady=5)
# 三点输入
self.intersect_entries = []
for i in range(3):
row = ttk.Frame(frame)
row.pack(fill=tk.X, pady=1)
ttk.Label(row, text=f"点{i+1} X:").pack(side=tk.LEFT)
x_entry = ttk.Entry(row, width=10)
x_entry.pack(side=tk.LEFT, padx=2)
ttk.Label(row, text="Y:").pack(side=tk.LEFT)
y_entry = ttk.Entry(row, width=10)
y_entry.pack(side=tk.LEFT, padx=2)
self.intersect_entries.append((x_entry, y_entry))
# 按钮区域
btn_frame = ttk.Frame(frame)
btn_frame.pack(fill=tk.X, pady=2)
ttk.Button(btn_frame, text="加载CSV", command=self.load_intersect_csv).pack(side=tk.LEFT, padx=2)
ttk.Button(btn_frame, text="计算", command=self.calculate_intersection).pack(side=tk.LEFT, padx=2)
def load_intersect_csv(self):
"""加载交点分解CSV文件"""
file_path = filedialog.askopenfilename(filetypes=[("CSV文件", "*.csv")])
if not file_path:
return
try:
with open(file_path, newline='') as f:
reader = csv.reader(f)
points = [tuple(map(float, row)) for row in reader if len(row) >= 2]
if len(points) < 3:
raise ValueError("需要至少3个有效坐标点")
for i, (x, y) in enumerate(points[:3]):
self.intersect_entries[i][0].delete(0, tk.END)
self.intersect_entries[i][0].insert(0, str(x))
self.intersect_entries[i][1].delete(0, tk.END)
self.intersect_entries[i][1].insert(0, str(y))
except Exception as e:
messagebox.showerror("文件错误", str(e))
def calculate_intersection(self):
"""执行交点分解计算"""
try:
points = [(float(x.get()), float(y.get()))
for x, y in self.intersect_entries]
inter1, inter2, center, radius = self.calculate_intersections(*points)
result = f"交点1: ({inter1[0]:.3f}, {inter1[1]:.3f})\n"
result += f"交点2: ({inter2[0]:.3f}, {inter2[1]:.3f})\n"
result += f"圆心坐标: ({center[0]:.3f}, {center[1]:.3f})\n"
result += f"半径: {radius:.3f}"
self.show_result(result)
except Exception as e:
messagebox.showerror("计算错误", str(e))
def calculate_intersections(self, p1, p2, p3):
"""计算回头曲线交点"""
x1, y1 = p1
x2, y2 = p2
x3, y3 = p3
# 计算中垂线方程参数
a1 = x2 - x1
b1 = y2 - y1
c1 = a1 * (x1 + x2) / 2 + b1 * (y1 + y2) / 2
a2 = x3 - x2
b2 = y3 - y2
c2 = a2 * (x2 + x3) / 2 + b2 * (y2 + y3) / 2
# 解圆心坐标
det = a1 * b2 - a2 * b1
if abs(det) < 1e-9:
raise ValueError("三点共线,无法形成圆")
h = (b2 * c1 - b1 * c2) / det
k = (a1 * c2 - a2 * c1) / det
radius = math.hypot(x1 - h, y1 - k)
# 计算交点坐标
alpha = math.atan2(y1 - k, x1 - h)
beta = math.atan2(y2 - k, x2 - h)
theta = 2 * (beta - alpha)
inter1 = (
h + radius * (math.cos(alpha) - math.tan(theta / 4) * math.sin(alpha)),
k + radius * (math.sin(alpha) + math.tan(theta / 4) * math.cos(alpha))
)
inter2 = (
h + radius * (2 * math.cos(beta) - math.cos(alpha) + math.tan(theta / 4) * math.sin(alpha)),
k + radius * (2 * math.sin(beta) - math.sin(alpha) - math.tan(theta / 4) * math.cos(alpha))
)
return inter1, inter2, (h, k), radius
def setup_padstone_input(self, parent):
"""垫石计算输入区域"""
frame = ttk.LabelFrame(parent, text="垫石计算", padding=10)
frame.pack(fill=tk.X, pady=5)
# 输入类型选择
self.padstone_input_type = tk.StringVar(value="两点")
type_frame = ttk.Frame(frame)
type_frame.pack(fill=tk.X, pady=2)
ttk.Label(type_frame, text="输入类型:").pack(side=tk.LEFT)
ttk.Combobox(type_frame, textvariable=self.padstone_input_type,
values=["两点", "四点"], state="readonly",
width=8).pack(side=tk.LEFT, padx=5)
self.padstone_input_type.trace_add("write", self.update_padstone_input_fields)
# 动态输入区域
self.padstone_input_frame = ttk.Frame(frame)
self.padstone_input_frame.pack(fill=tk.X, pady=2)
# 参数输入
param_frame = ttk.Frame(frame)
param_frame.pack(fill=tk.X, pady=2)
ttk.Label(param_frame, text="间距(m):").pack(side=tk.LEFT)
self.spacing_entry = ttk.Entry(param_frame, width=10)
self.spacing_entry.pack(side=tk.LEFT, padx=2)
ttk.Label(param_frame, text="垫石个数:").pack(side=tk.LEFT)
self.padstone_count_entry = ttk.Entry(param_frame, width=10)
self.padstone_count_entry.pack(side=tk.LEFT, padx=2)
# 按钮区域
btn_frame = ttk.Frame(frame)
btn_frame.pack(fill=tk.X, pady=2)
ttk.Button(btn_frame, text="加载CSV", command=self.load_padstone_csv).pack(side=tk.LEFT, padx=2)
ttk.Button(btn_frame, text="计算", command=self.calculate_padstone).pack(side=tk.LEFT, padx=2)
self.padstone_entries = []
self.update_padstone_input_fields()
def update_padstone_input_fields(self, *args):
"""更新垫石输入字段"""
for widget in self.padstone_input_frame.winfo_children():
widget.destroy()
self.padstone_entries.clear()
required_points = 2 if self.padstone_input_type.get() == "两点" else 4
for i in range(required_points):
row = ttk.Frame(self.padstone_input_frame)
row.pack(fill=tk.X, pady=1)
ttk.Label(row, text=f"点{i+1} X:").pack(side=tk.LEFT)
x_entry = ttk.Entry(row, width=10)
x_entry.pack(side=tk.LEFT, padx=2)
ttk.Label(row, text="Y:").pack(side=tk.LEFT)
y_entry = ttk.Entry(row, width=10)
y_entry.pack(side=tk.LEFT, padx=2)
self.padstone_entries.append((x_entry, y_entry))
def load_padstone_csv(self):
"""加载垫石CSV文件"""
file_path = filedialog.askopenfilename(filetypes=[("CSV文件", "*.csv")])
if not file_path:
return
try:
with open(file_path, newline='') as f:
reader = csv.reader(f)
points = [tuple(map(float, row)) for row in reader if len(row) >= 2]
required_points = 2 if self.padstone_input_type.get() == "两点" else 4
if len(points) < required_points:
raise ValueError(f"需要至少{required_points}个有效坐标点")
for i, (x, y) in enumerate(points[:required_points]):
self.padstone_entries[i][0].delete(0, tk.END)
self.padstone_entries[i][0].insert(0, str(x))
self.padstone_entries[i][1].delete(0, tk.END)
self.padstone_entries[i][1].insert(0, str(y))
except Exception as e:
messagebox.showerror("文件错误", str(e))
def calculate_padstone(self):
"""执行垫石计算"""
try:
points = [(float(x.get()), float(y.get()))
for x, y in self.padstone_entries]
spacing = float(self.spacing_entry.get())
padstone_count = int(self.padstone_count_entry.get())
if spacing <= 0:
raise ValueError("间距必须大于0")
if padstone_count <= 0:
raise ValueError("垫石个数必须大于0")
if len(points) == 2:
result = self.calculate_two_points_padstone(points, spacing, padstone_count)
else:
result = self.calculate_four_points_padstone(points, spacing, padstone_count)
self.show_result(result)
except Exception as e:
messagebox.showerror("计算错误", str(e))
def calculate_two_points_padstone(self, points, spacing, padstone_count):
"""两点计算模式"""
(x1, y1), (x2, y2) = points
mid_point = ((x1 + x2) / 2, (y1 + y2) / 2)
dx, dy = x2 - x1, y2 - y1
length = math.hypot(dx, dy)
if length == 0:
raise ValueError("输入点重合")
unit_vector = (dx / length, dy / length)
start_offset = -((padstone_count - 1) * spacing / 2)
padstone_coordinates = []
for i in range(padstone_count):
offset = start_offset + i * spacing
padstone_x = mid_point[0] + offset * unit_vector[0]
padstone_y = mid_point[1] + offset * unit_vector[1]
padstone_coordinates.append((padstone_x, padstone_y))
return "\n".join([f"垫石{i+1}坐标: ({x:.3f}, {y:.3f})" for i, (x, y) in enumerate(padstone_coordinates)])
def calculate_four_points_padstone(self, points, spacing, padstone_count):
"""四点计算模式"""
center_x = (points[0][0] + points[1][0] + points[2][0] + points[3][0]) / 4
center_y = (points[0][1] + points[1][1] + points[2][1] + points[3][1]) / 4
center_point = (center_x, center_y)
mid1 = ((points[0][0] + points[1][0]) / 2, (points[0][1] + points[1][1]) / 2)
mid2 = ((points[2][0] + points[3][0]) / 2, (points[2][1] + points[3][1]) / 2)
dx, dy = mid2[0] - mid1[0], mid2[1] - mid1[1]
length = math.hypot(dx, dy)
if length == 0:
raise ValueError("方向向量长度为0")
unit_vector = (dx / length, dy / length)
start_offset = -((padstone_count - 1) * spacing / 2)
padstone_coordinates = []
for i in range(padstone_count):
offset = start_offset + i * spacing
padstone_x = center_point[0] + offset * unit_vector[0]
padstone_y = center_point[1] + offset * unit_vector[1]
padstone_coordinates.append((padstone_x, padstone_y))
return "\n".join([f"垫石{i+1}坐标: ({x:.3f}, {y:.3f})" for i, (x, y) in enumerate(padstone_coordinates)])
def setup_column_width_input(self, parent):
"""柱宽计算输入区域"""
frame = ttk.LabelFrame(parent, text="柱宽计算", padding=10)
frame.pack(fill=tk.X, pady=5)
# 参数输入
param_frame = ttk.Frame(frame)
param_frame.pack(fill=tk.X, pady=2)
ttk.Label(param_frame, text="柱顶宽度(m):").pack(side=tk.LEFT)
self.top_entry = ttk.Entry(param_frame, width=10)
self.top_entry.pack(side=tk.LEFT, padx=2)
ttk.Label(param_frame, text="高差(m):").pack(side=tk.LEFT)
self.diff_entry = ttk.Entry(param_frame, width=10)
self.diff_entry.pack(side=tk.LEFT, padx=2)
# 计算按钮
ttk.Button(frame, text="计算", command=self.calculate_column_width).pack(pady=2)
def calculate_column_width(self):
"""执行柱宽计算"""
try:
top = float(self.top_entry.get())
diff = float(self.diff_entry.get())
width = top + diff / 40
result = f"柱宽 = {width:.3f} 米"
self.show_result(result)
except Exception as e:
messagebox.showerror("计算错误", str(e))
def setup_result_area(self, parent):
"""结果展示区域"""
# 结果文本区域
result_frame = ttk.LabelFrame(parent, text="计算结果", padding=10)
result_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 减小结果框的高度
self.result_text = tk.Text(result_frame, height=15, state='disabled')
scrollbar = ttk.Scrollbar(result_frame, command=self.result_text.yview)
self.result_text.configure(yscrollcommand=scrollbar.set)
self.result_text.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 按钮区域 - 放在结果框下方
button_frame = ttk.Frame(result_frame)
button_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5)
ttk.Button(button_frame, text="复制结果", command=self.copy_result).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="清空结果", command=self.clear_result).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="保存结果", command=self.save_result).pack(side=tk.LEFT, padx=5)
def show_result(self, result):
"""显示计算结果"""
self.result_text.config(state='normal')
self.result_text.insert(tk.END, result + "\n\n")
self.result_text.see(tk.END)
self.result_text.config(state='disabled')
def copy_result(self):
"""复制结果到剪贴板"""
result = self.result_text.get("1.0", tk.END).strip()
if result:
self.parent.clipboard_clear()
self.parent.clipboard_append(result)
messagebox.showinfo("复制成功", "结果已复制到剪贴板")
def clear_result(self):
"""清空结果"""
self.result_text.config(state='normal')
self.result_text.delete(1.0, tk.END)
self.result_text.config(state='disabled')
def save_result(self):
"""保存结果到文件"""
result = self.result_text.get("1.0", tk.END).strip()
if not result:
messagebox.showwarning("无数据", "没有结果可保存")
return
file_path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")])
if file_path:
try:
with open(file_path, 'w') as f:
f.write(result)
messagebox.showinfo("保存成功", f"结果已保存到 {file_path}")
except Exception as e:
messagebox.showerror("保存失败", str(e))
# === 三角高程 ===
class SurveyCalculator:
def __init__(self, parent):
self.parent = parent
self.setup_ui()
def setup_ui(self):
# 主容器
self.main_frame = ttk.Frame(self.parent, padding="10")
self.main_frame.pack(fill=tk.BOTH, expand=True)
# 标题
self.title_label = ttk.Label(
self.main_frame,
text="三角高程",
font=('Microsoft YaHei', 16, 'bold'),
foreground='#0066cc'
)
self.title_label.pack(pady=(0, 15))
# 输入区域
self.input_frame = ttk.LabelFrame(
self.main_frame,
text="观测数据输入",
padding=(15, 10)
)
self.input_frame.pack(fill=tk.X, pady=5, padx=5)
# 创建输入字段
self.create_input_fields()
# 结果区域
self.result_frame = ttk.LabelFrame(
self.main_frame,
text="计算结果",
padding="10"
)
self.result_frame.pack(fill=tk.BOTH, expand=True, pady=5, padx=5)
# 创建Treeview和滚动条
self.create_results_treeview()
# 按钮区域
self.button_frame = ttk.Frame(self.main_frame)
self.button_frame.pack(pady=(15, 5))
self.calculate_btn = ttk.Button(
self.button_frame,
text="计算",
command=self.calculate,
width=12,
)
self.calculate_btn.pack(side=tk.LEFT, padx=5)
self.clear_btn = ttk.Button(
self.button_frame,
text="清空",
command=self.clear_all,
width=12
)
self.clear_btn.pack(side=tk.LEFT, padx=5)
self.save_btn = ttk.Button(
self.button_frame,
text="保存为CSV",
command=self.save_to_csv,
width=12
)
self.save_btn.pack(side=tk.LEFT, padx=5)
# 状态栏
self.status_var = tk.StringVar()
self.status_bar = ttk.Label(
self.main_frame,
textvariable=self.status_var,
relief=tk.SUNKEN,
anchor=tk.W,
padding=(10, 5)
)
self.status_bar.pack(fill=tk.X, pady=(5, 0))
self.status_var.set("就绪 | 请输入观测数据并点击计算")
def create_input_fields(self):
"""创建输入字段"""
# 测站信息 - 第1行
ttk.Label(self.input_frame, text="测站名称:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
self.entry_station = ttk.Entry(self.input_frame, width=18)
self.entry_station.grid(row=0, column=1, padx=5, pady=5, sticky="w")
# 仪器高 - 第2行
ttk.Label(self.input_frame, text="仪器高:").grid(row=1, column=0, padx=5, pady=5, sticky="e")
self.entry_instrument_height = ttk.Entry(self.input_frame, width=18)
self.entry_instrument_height.grid(row=1, column=1, padx=5, pady=5, sticky="w")
ttk.Label(self.input_frame, text="m").grid(row=1, column=2, padx=(0, 15), pady=5, sticky="w")
# 目标信息 - 第3行
ttk.Label(self.input_frame, text="目标名称:").grid(row=2, column=0, padx=5, pady=5, sticky="e")
self.entry_target = ttk.Entry(self.input_frame, width=18)
self.entry_target.grid(row=2, column=1, padx=5, pady=5, sticky="w")
# 觇高 - 第4行
ttk.Label(self.input_frame, text="觇高:").grid(row=3, column=0, padx=5, pady=5, sticky="e")
self.entry_target_height = ttk.Entry(self.input_frame, width=18)
self.entry_target_height.grid(row=3, column=1, padx=5, pady=5, sticky="w")
ttk.Label(self.input_frame, text="m").grid(row=3, column=2, padx=(0, 15), pady=5, sticky="w")
# 平距 - 第5行
ttk.Label(self.input_frame, text="平距:").grid(row=4, column=0, padx=5, pady=5, sticky="e")
self.entry_distance = ttk.Entry(self.input_frame, width=18)
self.entry_distance.grid(row=4, column=1, padx=5, pady=5, sticky="w")
ttk.Label(self.input_frame, text="m").grid(row=4, column=2, padx=(0, 15), pady=5, sticky="w")
# 分隔线
ttk.Separator(self.input_frame, orient=tk.VERTICAL).grid(row=0, column=3, rowspan=5, padx=15, sticky="ns")
# 左天顶距 - 第1行右侧
ttk.Label(self.input_frame, text="左天顶距:").grid(row=0, column=4, padx=5, pady=5, sticky="e")
self.entry_zenith_left_deg = ttk.Entry(self.input_frame, width=6)
self.entry_zenith_left_deg.grid(row=0, column=5, padx=2, pady=5)
ttk.Label(self.input_frame, text="°").grid(row=0, column=6, padx=2, pady=5, sticky="w")
self.entry_zenith_left_min = ttk.Entry(self.input_frame, width=6)
self.entry_zenith_left_min.grid(row=0, column=7, padx=2, pady=5)
ttk.Label(self.input_frame, text="'").grid(row=0, column=8, padx=2, pady=5, sticky="w")
self.entry_zenith_left_sec = ttk.Entry(self.input_frame, width=8)
self.entry_zenith_left_sec.grid(row=0, column=9, padx=2, pady=5)
ttk.Label(self.input_frame, text='"').grid(row=0, column=10, padx=(2, 15), pady=5, sticky="w")
# 右天顶距 - 第2行右侧
ttk.Label(self.input_frame, text="右天顶距:").grid(row=1, column=4, padx=5, pady=5, sticky="e")
self.entry_zenith_right_deg = ttk.Entry(self.input_frame, width=6)
self.entry_zenith_right_deg.grid(row=1, column=5, padx=2, pady=5)
ttk.Label(self.input_frame, text="°").grid(row=1, column=6, padx=2, pady=5, sticky="w")
self.entry_zenith_right_min = ttk.Entry(self.input_frame, width=6)
self.entry_zenith_right_min.grid(row=1, column=7, padx=2, pady=5)
ttk.Label(self.input_frame, text="'").grid(row=1, column=8, padx=2, pady=5, sticky="w")
self.entry_zenith_right_sec = ttk.Entry(self.input_frame, width=8)
self.entry_zenith_right_sec.grid(row=1, column=9, padx=2, pady=5)
ttk.Label(self.input_frame, text='"').grid(row=1, column=10, padx=(2, 15), pady=5, sticky="w")
# 添加说明标签
note_label = ttk.Label(
self.input_frame,
text="提示:天顶距可以输入度分秒(如90°30'15.00\")或十进制(如90.5042)",
foreground="#666666",
font=('Microsoft YaHei', 9)
)
note_label.grid(row=3, column=4, columnspan=7, pady=(10, 0), sticky="w")
# 配置列权重,使输入区域均匀分布
for i in range(11):
self.input_frame.columnconfigure(i, weight=1 if i in (1, 5, 7, 9) else 0)
def create_results_treeview(self):
"""创建结果表格和滚动条"""
# 创建滚动条
tree_scroll = ttk.Scrollbar(self.result_frame)
tree_scroll.pack(side=tk.RIGHT, fill=tk.Y)
# 创建Treeview
self.tree = ttk.Treeview(
self.result_frame,
columns=("测站", "仪高(m)", "目标", "觇高(m)", "平距(m)", "左天顶距", "右天顶距", "竖直角α", "指标差", "高差(m)"),
show="headings",
yscrollcommand=tree_scroll.set,
height=10
)
self.tree.pack(fill=tk.BOTH, expand=True)
# 配置滚动条
tree_scroll.config(command=self.tree.yview)
# 设置列标题
columns = {
"测站": 80,
"仪高(m)": 80,
"目标": 80,
"觇高(m)": 80,
"平距(m)": 80,
"左天顶距": 120,
"右天顶距": 120,
"竖直角α": 120,
"指标差": 100,
"高差(m)": 80
}
for col, width in columns.items():
self.tree.heading(col, text=col)
self.tree.column(col, width=width, anchor=tk.CENTER)
def calculate(self):
"""执行计算"""
try:
self.status_var.set("计算中...")
self.parent.update()
# 获取输入值
station = self.entry_station.get().strip()
if not station:
raise ValueError("测站名称不能为空")
instrument_height = Utils.validate_float(self.entry_instrument_height.get(), "仪高")
target = self.entry_target.get().strip()
if not target:
raise ValueError("目标名称不能为空")
target_height = Utils.validate_float(self.entry_target_height.get(), "觇高")
distance = Utils.validate_float(self.entry_distance.get(), "平距")
# 获取度分秒值
zenith_left = self.get_dms_value(
self.entry_zenith_left_deg,
self.entry_zenith_left_min,
self.entry_zenith_left_sec,
"左天顶距"
)
zenith_right = self.get_dms_value(
self.entry_zenith_right_deg,
self.entry_zenith_right_min,
self.entry_zenith_right_sec,
"右天顶距"
)
# 计算竖直角
alpha_left = 90 - zenith_left
alpha_right = zenith_right - 270
average_angle = (alpha_left + alpha_right) / 2
# 计算指标差
index_error = (zenith_left + zenith_right - 360) / 2
# 计算高差
tan_alpha = math.tan(math.radians(average_angle))
h = distance * tan_alpha
height_difference = h + instrument_height - target_height
# 转换为度分秒格式
zenith_left_dms = self.decimal_to_dms(zenith_left)
zenith_right_dms = self.decimal_to_dms(zenith_right)
average_angle_dms = self.decimal_to_dms(average_angle)
index_error_dms = self.decimal_to_dms(index_error)
# 显示结果
self.tree.insert("", "end", values=(
station,
f"{instrument_height:.3f}",
target,
f"{target_height:.3f}",
f"{distance:.3f}",
f"{zenith_left_dms[0]:03.0f}°{zenith_left_dms[1]:02.0f}'{zenith_left_dms[2]:05.2f}\"",
f"{zenith_right_dms[0]:03.0f}°{zenith_right_dms[1]:02.0f}'{zenith_right_dms[2]:05.2f}\"",
f"{average_angle_dms[0]:+03.0f}°{average_angle_dms[1]:02.0f}'{average_angle_dms[2]:05.2f}\"",
f"{index_error_dms[0]:+03.0f}°{index_error_dms[1]:02.0f}'{index_error_dms[2]:05.2f}\"",
f"{height_difference:.3f}"
))
self.status_var.set("计算完成")
except ValueError as e:
messagebox.showerror("输入错误", str(e))
self.status_var.set(f"输入错误 | {str(e)}")
except Exception as e:
messagebox.showerror("计算错误", f"发生意外错误: {str(e)}")
self.status_var.set(f"错误 | {str(e)}")
def clear_all(self):
"""清空所有输入字段和结果"""
self.clear_inputs()
for item in self.tree.get_children():
self.tree.delete(item)
self.status_var.set("已清空 | 请输入观测数据并点击计算")
def clear_inputs(self):
"""清空所有输入字段"""
for entry in [
self.entry_station,
self.entry_instrument_height,
self.entry_target,
self.entry_target_height,
self.entry_distance,
self.entry_zenith_left_deg,
self.entry_zenith_left_min,
self.entry_zenith_left_sec,
self.entry_zenith_right_deg,
self.entry_zenith_right_min,
self.entry_zenith_right_sec
]:
entry.delete(0, tk.END)
def get_dms_value(self, deg_entry, min_entry, sec_entry, field_name):
"""获取度分秒值并转换为十进制度数"""
try:
deg = float(deg_entry.get() or 0)
minutes = float(min_entry.get() or 0)
seconds = float(sec_entry.get() or 0)
return self.dms_to_decimal(deg, minutes, seconds)
except ValueError:
raise ValueError(f"请输入有效的度分秒数值: {field_name}")
def save_to_csv(self):
"""保存计算结果为CSV文件"""
if not self.tree.get_children():
messagebox.showwarning("无数据", "没有可保存的计算结果")
return
file_path = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")],
title="保存计算结果"
)
if file_path:
try:
with open(file_path, 'w', newline='', encoding='utf-8-sig') as csvfile:
writer = csv.writer(csvfile)
# 写入表头
writer.writerow(["测站", "仪高(m)", "目标", "觇高(m)", "平距(m)", "左天顶距", "右天顶距", "竖直角α", "指标差", "高差(m)"])
# 写入数据
for item in self.tree.get_children():
row = self.tree.item(item)
writer.writerow(row['values'])
self.status_var.set(f"结果已保存到: {file_path}")
messagebox.showinfo("保存成功", f"数据已成功保存到:\n{file_path}")
except Exception as e:
messagebox.showerror("保存错误", f"保存文件时出错: {str(e)}")
self.status_var.set("保存失败")
@staticmethod
def dms_to_decimal(degrees, minutes=0, seconds=0):
"""将度分秒转换为十进制度数"""
sign = 1 if degrees >= 0 else -1
return degrees + sign * minutes / 60 + sign * seconds / 3600
@staticmethod
def decimal_to_dms(decimal_angle):
"""将十进制度数转换为度分秒"""
sign = 1 if decimal_angle >= 0 else -1
decimal_angle = abs(decimal_angle)
degrees = int(decimal_angle)
remaining = (decimal_angle - degrees) * 60
minutes = int(remaining)
seconds = round((remaining - minutes) * 60, 2)
# 处理60秒进位
if seconds >= 60:
seconds -= 60
minutes += 1
if minutes >= 60:
minutes -= 60
degrees += 1
return sign * degrees, minutes, seconds
# === 角度、曲率 ===
class CombinedCalculator:
def __init__(self, parent):
self.parent = parent
self.setup_ui()
def setup_ui(self):
# 主框架
main_frame = ttk.Frame(self.parent)
main_frame.pack(padx=10, pady=10, fill='both', expand=True)
# 缓和曲线曲率计算器部分
curvature_frame = ttk.LabelFrame(main_frame, text="缓和曲线曲率计算器")
curvature_frame.pack(fill='both', expand=True, padx=10, pady=10)
# 参数输入部分
self.create_curvature_widgets(curvature_frame)
# 角度加减计算器部分
angle_frame = ttk.LabelFrame(main_frame, text="角度加减计算器")
angle_frame.pack(fill='both', expand=True, padx=10, pady=10)
self.create_angle_widgets(angle_frame)
def create_curvature_widgets(self, frame):
# 参数输入部分
input_frame = ttk.Frame(frame)
input_frame.pack(padx=10, pady=10, fill='both', expand=True)
ttk.Label(input_frame, text="缓和曲线参数A(m):").grid(row=0, column=0, padx=5, pady=5, sticky="e")
self.A_entry = ttk.Entry(input_frame, width=15)
self.A_entry.grid(row=0, column=1, padx=5, pady=5, sticky="w")
ttk.Label(input_frame, text="缓和曲线长度Ls(m):").grid(row=1, column=0, padx=5, pady=5, sticky="e")
self.Ls_entry = ttk.Entry(input_frame, width=15)
self.Ls_entry.grid(row=1, column=1, padx=5, pady=5, sticky="w")
self.known_var = tk.StringVar(value="start")
ttk.Label(input_frame, text="已知半径端:").grid(row=2, column=0, padx=5, pady=5, sticky="e")
ttk.Radiobutton(input_frame, text="起点半径", variable=self.known_var,
value="start", command=self.toggle_radius_input).grid(row=2, column=1, sticky="w")
ttk.Radiobutton(input_frame, text="终点半径", variable=self.known_var,
value="end", command=self.toggle_radius_input).grid(row=2, column=2, sticky="w")
self.radius_frame = ttk.Frame(input_frame)
self.radius_frame.grid(row=3, column=0, columnspan=3, pady=5, sticky="ew")
self.start_radius_label = ttk.Label(self.radius_frame, text="起点半径R_start(m):")
self.start_radius_entry = ttk.Entry(self.radius_frame, width=15)
self.end_radius_label = ttk.Label(self.radius_frame, text="终点半径R_end(m):")
self.end_radius_entry = ttk.Entry(self.radius_frame, width=15)
self.show_start_radius_input()
ttk.Button(input_frame, text="计算", command=self.calculate_curvature).grid(row=4, column=0, columnspan=3, pady=10)
self.result_label = ttk.Label(input_frame, text="", font=('Arial', 10), wraplength=400)
self.result_label.grid(row=5, column=0, columnspan=3, sticky="nsew")
ttk.Button(input_frame, text="复制结果", command=self.copy_result).grid(row=6, column=0, columnspan=3, pady=5)
def toggle_radius_input(self):
if self.known_var.get() == "start":
self.show_start_radius_input()
else:
self.show_end_radius_input()
def show_start_radius_input(self):
self.end_radius_label.grid_remove()
self.end_radius_entry.grid_remove()
self.start_radius_label.grid(row=0, column=0, padx=5)
self.start_radius_entry.grid(row=0, column=1, padx=5)
def show_end_radius_input(self):
self.start_radius_label.grid_remove()
self.start_radius_entry.grid_remove()
self.end_radius_label.grid(row=0, column=0, padx=5)
self.end_radius_entry.grid(row=0, column=1, padx=5)
def calculate_curvature(self):
try:
A = Utils.validate_float(self.A_entry.get(), "参数A")
if A <= 0:
raise ValueError("参数A必须为正数")
Ls = Utils.validate_float(self.Ls_entry.get(), "Ls")
if Ls <= 0:
raise ValueError("Ls必须为正数")
if self.known_var.get() == "start":
R_start = Utils.validate_float(self.start_radius_entry.get(), "起点半径")
if R_start <= 0:
raise ValueError("起点半径必须为正数")
R_end = None
else:
R_end = Utils.validate_float(self.end_radius_entry.get(), "终点半径")
if R_end <= 0:
raise ValueError("终点半径必须为正数")
R_start = None
calc_start, calc_end = self.calculate_curvature_logic(A, Ls, R_start, R_end)
result_text = f"计算结果:\n起点半径 R_start = {calc_start:.2f} m\n终点半径 R_end = {calc_end:.2f} m"
self.result_label.config(text=result_text)
except ValueError as e:
messagebox.showerror("输入错误", str(e))
except Exception as e:
messagebox.showerror("计算错误", f"发生意外错误: {str(e)}")
def calculate_curvature_logic(self, A, Ls, R_start=None, R_end=None):
A_sq = A ** 2
if R_start is not None:
denominator = A_sq + R_start * Ls
if denominator <= 0:
raise ValueError(f"参数不合法: A² + R_start*Ls = {denominator} ≤ 0")
R_end = (A_sq * R_start) / denominator
return round(R_start, 2), round(R_end, 2)
elif R_end is not None:
denominator = A_sq - R_end * Ls
if denominator <= 0:
if denominator == 0:
raise ValueError("完整缓和曲线情形(R_start→∞),应直接使用 R = A²/Ls")
raise ValueError(f"参数不合法: A² - R_end*Ls = {denominator} ≤ 0")
R_start = (A_sq * R_end) / denominator
return round(R_start, 2), round(R_end, 2)
else:
raise ValueError("必须指定起点或终点半径")
def copy_result(self):
result_text = self.result_label.cget("text")
if result_text:
self.parent.clipboard_clear()
self.parent.clipboard_append(result_text)
messagebox.showinfo("复制成功", "结果已复制到剪贴板")
def create_angle_widgets(self, frame):
# 角度输入部分
self.create_angle_input(frame, "角度1", 0)
self.create_angle_input(frame, "角度2", 1)
# 操作选择
self.operation_var = tk.StringVar(value="加")
ttk.Radiobutton(frame, text="加", variable=self.operation_var, value="加").grid(row=2, column=1, sticky="w")
ttk.Radiobutton(frame, text="减", variable=self.operation_var, value="减").grid(row=2, column=2, sticky="w")
# 计算按钮和结果
ttk.Button(frame, text="计算", command=self.calculate_angle).grid(row=3, column=0, columnspan=4, pady=10)
self.angle_result_label = ttk.Label(frame, text="", font=('Arial', 10))
self.angle_result_label.grid(row=4, column=0, columnspan=4, sticky="w")
def create_angle_input(self, frame, label_text, row):
"""创建角度输入行"""
ttk.Label(frame, text=label_text + ":").grid(row=row, column=0, padx=5, pady=2, sticky="e")
entries = []
for i, unit in enumerate(["度", "分", "秒"]):
entry = ttk.Entry(frame, width=5)
entry.grid(row=row, column=i + 1, padx=2, pady=2)
ttk.Label(frame, text=unit).grid(row=row, column=i + 1, padx=(25, 5), sticky="e")
entries.append(entry)
# 保存为实例变量
setattr(self, f"angle{row + 1}_entries", entries)
def calculate_angle(self):
try:
angle1 = self.get_angle_values(0)
angle2 = self.get_angle_values(1)
total_sec1 = self.dms_to_seconds(*angle1)
total_sec2 = self.dms_to_seconds(*angle2)
if self.operation_var.get() == "加":
result_sec = total_sec1 + total_sec2
else:
result_sec = total_sec1 - total_sec2
if result_sec < 0:
result_sec = abs(result_sec)
sign = "-"
else:
sign = ""
degrees, minutes, seconds = self.seconds_to_dms(result_sec)
self.angle_result_label.config(text=f"结果: {sign}{degrees}° {minutes}' {seconds}\"")
except ValueError:
messagebox.showerror("输入错误", "请输入有效的数字!")
def get_angle_values(self, angle_num):
"""获取指定角度的度分秒值"""
entries = getattr(self, f"angle{angle_num + 1}_entries")
return [float(entry.get()) for entry in entries]
def dms_to_seconds(self, degrees, minutes, seconds):
"""度分秒转换为总秒数"""
return int(degrees * 3600 + minutes * 60 + seconds)
def seconds_to_dms(self, total_seconds):
"""总秒数转换为度分秒(自动进位)"""
degrees = total_seconds // 3600
remaining = total_seconds % 3600
minutes = remaining // 60
seconds = remaining % 60
return degrees, minutes, seconds
# === 隧道计算 ===
class CompactTunnelCalculator:
def __init__(self, parent):
self.parent = parent
self.setup_ui()
def setup_ui(self):
# 主框架
main_frame = ttk.Frame(self.parent)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 左侧输入区域
input_frame = ttk.Frame(main_frame)
input_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)
# 创建三个计算区域的输入部分
self.create_single_input(input_frame)
self.create_multi_input(input_frame)
self.create_points_input(input_frame)
# 右侧结果区域
result_frame = ttk.Frame(main_frame)
result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
# 创建结果展示区域
self.create_result_area(result_frame)
def create_single_input(self, parent):
"""创建正洞计算输入区域"""
frame = ttk.LabelFrame(parent, text="正洞计算", padding=10)
frame.pack(fill=tk.X, pady=5)
# 计算模式选择
self.single_mode_var = tk.StringVar(value="随机值")
ttk.Label(frame, text="计算模式:").grid(row=0, column=0, sticky="e", padx=5, pady=2)
ttk.OptionMenu(frame, self.single_mode_var, "随机值", "随机值", "固定值",
command=self.toggle_single_mode).grid(row=0, column=1, sticky="ew", padx=5, pady=2)
# 基础半径输入
ttk.Label(frame, text="基础半径值:").grid(row=1, column=0, sticky="e", padx=5, pady=2)
self.base_radius_entry = ttk.Entry(frame)
self.base_radius_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=2)
# 半径文件输入 (默认隐藏)
self.radius_file_frame = ttk.Frame(frame)
self.radius_file_frame.grid(row=2, column=0, columnspan=2, sticky="ew", padx=5, pady=2)
self.radius_file_frame.grid_remove()
ttk.Label(self.radius_file_frame, text="半径数据文件:").pack(side=tk.LEFT, padx=5)
self.radius_file_entry = ttk.Entry(self.radius_file_frame)
self.radius_file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
ttk.Button(self.radius_file_frame, text="浏览...", command=self.browse_radius_file).pack(side=tk.LEFT, padx=5)
# 偏移量和点数输入
params = [
("X偏移量:", "x_offset_entry", "5.32"),
("Y偏移量:", "y_offset_entry", "1.61"),
("点数:", "points_entry", "20")
]
for i, (label, name, default) in enumerate(params, start=3):
ttk.Label(frame, text=label).grid(row=i, column=0, sticky="e", padx=5, pady=2)
entry = ttk.Entry(frame)
entry.insert(0, default)
entry.grid(row=i, column=1, sticky="ew", padx=5, pady=2)
setattr(self, name, entry)
# 计算按钮
ttk.Button(frame, text="计算正洞", command=self.calculate_single).grid(
row=6, column=0, columnspan=2, pady=5, sticky="ew")
def create_multi_input(self, parent):
"""创建紧急停车带计算输入区域"""
frame = ttk.LabelFrame(parent, text="紧急停车带计算", padding=10)
frame.pack(fill=tk.X, pady=5)
# 计算模式选择
self.multi_mode_var = tk.StringVar(value="随机值")
ttk.Label(frame, text="计算模式:").grid(row=0, column=0, sticky="e", padx=5, pady=2)
ttk.OptionMenu(frame, self.multi_mode_var, "随机值", "随机值", "固定值",
command=self.toggle_multi_mode).grid(row=0, column=1, sticky="ew", padx=5, pady=2)
# 半径类型选择
ttk.Label(frame, text="半径类型:").grid(row=1, column=0, sticky="e", padx=5, pady=2)
self.radius_type_var = tk.StringVar()
ttk.Combobox(frame, textvariable=self.radius_type_var,
values=["5jt", "4jt", "二衬"], state="readonly").grid(row=1, column=1, sticky="ew", padx=5, pady=2)
self.radius_type_var.set("5jt")
# 半径文件输入 (默认隐藏)
self.multi_file_frame = ttk.Frame(frame)
self.multi_file_frame.grid(row=2, column=0, columnspan=2, sticky="ew", padx=5, pady=2)
self.multi_file_frame.grid_remove()
ttk.Label(self.multi_file_frame, text="半径数据文件:").pack(side=tk.LEFT, padx=5)
self.multi_file_entry = ttk.Entry(self.multi_file_frame)
self.multi_file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
ttk.Button(self.multi_file_frame, text="浏览...", command=self.browse_multi_file).pack(side=tk.LEFT, padx=5)
# 偏移量输入
params = [
("X偏移量:", "multi_x_offset_entry", "6.82"),
("Y偏移量:", "multi_y_offset_entry", "1.61")
]
for i, (label, name, default) in enumerate(params, start=3):
ttk.Label(frame, text=label).grid(row=i, column=0, sticky="e", padx=5, pady=2)
entry = ttk.Entry(frame)
entry.insert(0, default)
entry.grid(row=i, column=1, sticky="ew", padx=5, pady=2)
setattr(self, name, entry)
# 计算按钮
ttk.Button(frame, text="计算停车带", command=self.calculate_multi).grid(
row=5, column=0, columnspan=2, pady=5, sticky="ew")
def create_points_input(self, parent):
"""创建隧道计算待测点计算输入区域"""
frame = ttk.LabelFrame(parent, text="隧道计算待测点计算", padding=10)
frame.pack(fill=tk.X, pady=5)
# 输入字段
fields = [
("点1 X (测设线点):", "x1_entry"),
("点1 Y (测设线点):", "y1_entry"),
("点2 X (衬砌中线点):", "x2_entry"),
("点2 Y (衬砌中线点):", "y2_entry"),
("测设线设计高程 (m):", "elevation_entry")
]
for i, (label, name) in enumerate(fields):
ttk.Label(frame, text=label).grid(row=i, column=0, sticky="e", padx=5, pady=2)
entry = ttk.Entry(frame)
entry.grid(row=i, column=1, sticky="ew", padx=5, pady=2)
setattr(self, name, entry)
# 批量输入区域
ttk.Label(frame, text="批量距离d和高差 (距离,高差):").grid(
row=5, column=0, padx=5, pady=2, sticky="e")
self.bulk_entry = tk.Text(frame, height=3, width=30)
self.bulk_entry.grid(row=5, column=1, padx=5, pady=2, sticky="ew")
# 按钮区域
button_frame = ttk.Frame(frame)
button_frame.grid(row=6, column=0, columnspan=2, pady=5, sticky="ew")
ttk.Button(button_frame, text="加载CSV", command=self.load_csv).pack(side=tk.LEFT, padx=2)
ttk.Button(button_frame, text="计算待测点", command=self.calculate_points).pack(side=tk.LEFT, padx=2)
def create_result_area(self, parent):
"""创建结果展示区域"""
# 创建一个垂直布局的框架,将坐标计算结果和待测点结果分开
results_container = ttk.Frame(parent)
results_container.pack(fill=tk.BOTH, expand=True)
# 坐标计算结果区域
calc_frame = ttk.LabelFrame(results_container, text="坐标计算结果", padding=10)
calc_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 创建树形视图和滚动条
self.tree = ttk.Treeview(calc_frame, columns=("angle", "radius", "x", "y", "x_prime", "xy_pair", "xprime_y_pair"),
show="headings", height=15)
vsb = ttk.Scrollbar(calc_frame, orient="vertical", command=self.tree.yview)
hsb = ttk.Scrollbar(calc_frame, orient="horizontal", command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
# 设置列标题
headers = ("角度(°)", "半径", "X坐标", "Y坐标", "X'坐标", "X,Y", "X',Y")
for col, header in zip(self.tree["columns"], headers):
self.tree.heading(col, text=header)
self.tree.column(col, width=100, anchor=tk.CENTER)
# 布局
self.tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
# 按钮区域
button_frame = ttk.Frame(calc_frame)
button_frame.grid(row=2, column=0, columnspan=2, pady=5, sticky="ew")
ttk.Button(button_frame, text="保存CSV", command=self.save_results_csv).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="清空结果", command=self.clear_results).pack(side=tk.LEFT, padx=5)
# 待测点结果区域
points_frame = ttk.LabelFrame(results_container, text="待测点结果", padding=10)
points_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 待测点结果显示区域
self.points_text = tk.Text(points_frame, height=5, state='disabled')
scrollbar = ttk.Scrollbar(points_frame, command=self.points_text.yview)
self.points_text.configure(yscrollcommand=scrollbar.set)
# 使用grid布局管理器
self.points_text.grid(row=0, column=0, sticky="nsew", pady=(0, 5))
scrollbar.grid(row=0, column=1, sticky="ns", pady=(0, 5))
# 配置网格权重
points_frame.grid_rowconfigure(0, weight=1)
points_frame.grid_columnconfigure(0, weight=1)
# 待测点结果按钮 - 放在结果框下方
button_frame = ttk.Frame(points_frame)
button_frame.grid(row=1, column=0, columnspan=2, sticky="ew")
ttk.Button(button_frame, text="复制结果", command=self.copy_points_result).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="清空结果", command=self.clear_points_results).pack(side=tk.LEFT, padx=5)
def toggle_single_mode(self, mode):
"""切换正洞计算模式"""
if mode == "随机值":
self.base_radius_entry.grid()
self.radius_file_frame.grid_remove()
else:
self.base_radius_entry.grid_remove()
self.radius_file_frame.grid()
def toggle_multi_mode(self, mode):
"""切换多半径计算模式"""
if mode == "随机值":
self.radius_type_var.set("5jt")
self.multi_file_frame.grid_remove()
else:
self.multi_file_frame.grid()
def browse_radius_file(self):
"""浏览半径数据文件"""
filename = filedialog.askopenfilename(filetypes=[("CSV文件", "*.csv")])
if filename:
self.radius_file_entry.delete(0, tk.END)
self.radius_file_entry.insert(0, filename)
def browse_multi_file(self):
"""浏览多半径数据文件"""
filename = filedialog.askopenfilename(filetypes=[("CSV文件", "*.csv")])
if filename:
self.multi_file_entry.delete(0, tk.END)
self.multi_file_entry.insert(0, filename)
def load_csv(self):
"""加载CSV文件"""
file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
if not file_path:
return
try:
with open(file_path, newline='') as csvfile:
points = []
for row in csv.reader(csvfile):
if len(row) != 2:
raise ValueError("每行必须包含两个坐标值")
points.append((float(row[0]), float(row[1])))
if len(points) < 2:
raise ValueError("需要至少两个点")
self.x1_entry.delete(0, tk.END)
self.y1_entry.delete(0, tk.END)
self.x1_entry.insert(0, points[0][0]) # 修正:正确读取 x1
self.y1_entry.insert(0, points[0][1]) # 修正:正确读取 y1
self.x2_entry.delete(0, tk.END)
self.y2_entry.delete(0, tk.END)
self.x2_entry.insert(0, points[1][0]) # 修正:正确读取 x2
self.y2_entry.insert(0, points[1][1]) # 修正:正确读取 y2
except Exception as e:
messagebox.showerror("错误", str(e))
def calculate_single(self):
"""执行正洞计算"""
try:
# 获取输入参数
x_offset = Utils.validate_float(self.x_offset_entry.get(), "X偏移量")
y_offset = Utils.validate_float(self.y_offset_entry.get(), "Y偏移量")
point_count = int(self.points_entry.get())
if self.single_mode_var.get() == "随机值":
base_radius = Utils.validate_float(self.base_radius_entry.get(), "基础半径")
results = self.calculate_single_random(base_radius, point_count, x_offset, y_offset)
else:
filename = self.radius_file_entry.get()
if not filename:
raise ValueError("请选择半径数据文件")
with open(filename, 'r') as f:
radius_values = [float(row[0]) for row in csv.reader(f) if row]
results = self.calculate_single_fixed(radius_values, x_offset, y_offset)
# 显示结果
self.tree.delete(*self.tree.get_children())
for row in results:
self.tree.insert("", tk.END, values=row)
except Exception as e:
messagebox.showerror("计算错误", str(e))
def calculate_single_random(self, base_radius, point_count, x_offset, y_offset):
# 示例:随机生成正洞坐标
results = []
for i in range(point_count):
angle = i * (180 / (point_count - 1))
radius = base_radius + random.uniform(-0.1, 0.1) # 随机调整半径
x = x_offset + radius * math.cos(math.radians(angle))
y = y_offset + radius * math.sin(math.radians(angle))
x_prime = x_offset - radius * math.cos(math.radians(angle))
results.append((angle, radius, x, y, x_prime, f"{x:.4f},{y:.4f}", f"{x_prime:.4f},{y:.4f}"))
return results
def calculate_single_fixed(self, radius_values, x_offset, y_offset):
# 示例:使用固定半径值计算正洞坐标
results = []
for i, radius in enumerate(radius_values):
angle = i * (180 / (len(radius_values) - 1))
x = x_offset + radius * math.cos(math.radians(angle))
y = y_offset + radius * math.sin(math.radians(angle))
x_prime = x_offset - radius * math.cos(math.radians(angle))
results.append((angle, radius, x, y, x_prime, f"{x:.4f},{y:.4f}", f"{x_prime:.4f},{y:.4f}"))
return results
def calculate_multi(self):
"""执行多半径计算"""
try:
# 获取输入参数
x_offset = Utils.validate_float(self.multi_x_offset_entry.get(), "X偏移量")
y_offset = Utils.validate_float(self.multi_y_offset_entry.get(), "Y偏移量")
if self.multi_mode_var.get() == "随机值":
radius_type = self.radius_type_var.get()
results = self.calculate_multi_random(radius_type, x_offset, y_offset)
else:
filename = self.multi_file_entry.get()
if not filename:
raise ValueError("请选择半径数据文件")
with open(filename, 'r') as f:
radius_values = [float(row[0]) for row in csv.reader(f) if row]
results = self.calculate_multi_fixed(radius_values, x_offset, y_offset)
# 显示结果
self.tree.delete(*self.tree.get_children())
for row in results:
self.tree.insert("", tk.END, values=row)
except Exception as e:
messagebox.showerror("计算错误", str(e))
def calculate_multi_random(self, radius_type, x_offset, y_offset):
# 示例:随机生成紧急停车带坐标
results = []
for i in range(20): # 示例:生成20个点
angle = i * (180 / 19)
radius = 8.0 + random.uniform(-0.1, 0.1) # 随机调整半径
x = x_offset + radius * math.cos(math.radians(angle))
y = y_offset + radius * math.sin(math.radians(angle))
x_prime = x_offset - radius * math.cos(math.radians(angle))
results.append((angle, radius, x, y, x_prime, f"{x:.4f},{y:.4f}", f"{x_prime:.4f},{y:.4f}"))
return results
def calculate_multi_fixed(self, radius_values, x_offset, y_offset):
# 示例:使用固定半径值计算紧急停车带坐标
results = []
for i, radius in enumerate(radius_values):
angle = i * (180 / (len(radius_values) - 1))
x = x_offset + radius * math.cos(math.radians(angle))
y = y_offset + radius * math.sin(math.radians(angle))
x_prime = x_offset - radius * math.cos(math.radians(angle))
results.append((angle, radius, x, y, x_prime, f"{x:.4f},{y:.4f}", f"{x_prime:.4f},{y:.4f}"))
return results
def calculate_points(self):
"""计算待测点"""
try:
# 获取输入值
x1 = Utils.validate_float(self.x1_entry.get(), "X1")
y1 = Utils.validate_float(self.y1_entry.get(), "Y1")
x2 = Utils.validate_float(self.x2_entry.get(), "X2")
y2 = Utils.validate_float(self.y2_entry.get(), "Y2")
elevation = Utils.validate_float(self.elevation_entry.get(), "高程")
L = math.hypot(x2 - x1, y2 - y1)
if L == 0:
raise ValueError("点1和点2重合")
# 处理批量数据
bulk_data = self.bulk_entry.get("1.0", tk.END).strip()
if not bulk_data:
raise ValueError("请输入批量距离和高差数据")
data_pairs = []
for line in bulk_data.split('\n'):
if line.strip():
parts = line.split(',')
if len(parts) != 2:
raise ValueError("每行必须包含两个值(距离,高差)")
d = Utils.validate_float(parts[0], "距离")
height_diff = Utils.validate_float(parts[1], "高差")
data_pairs.append((d, height_diff))
# 计算结果
results = []
for d, height_diff in data_pairs:
t = d / L
x = x1 + t * (x2 - x1)
y = y1 + t * (y2 - y1)
results.append((x, y, elevation + height_diff))
# 显示结果
self.points_text.config(state='normal')
self.points_text.delete(1.0, tk.END)
result_str = "\n".join(
f"待测点 {i+1}: {x:.3f}, {y:.3f}, 高程: {e:.3f} m"
for i, (x, y, e) in enumerate(results))
self.points_text.insert(tk.END, result_str)
self.points_text.config(state='disabled')
except ValueError as e:
messagebox.showerror("输入错误", str(e))
def save_results_csv(self):
"""保存计算结果到CSV"""
if not self.tree.get_children():
messagebox.showwarning("无数据", "没有计算结果可保存")
return
try:
filename = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV文件", "*.csv")],
title="保存计算结果")
if not filename:
return
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([self.tree.heading(col)['text'] for col in self.tree['columns']])
for item in self.tree.get_children():
writer.writerow(self.tree.item(item)['values'])
messagebox.showinfo("保存成功", f"结果已保存到 {filename}")
except Exception as e:
messagebox.showerror("保存错误", str(e))
def clear_results(self):
"""清空坐标计算结果"""
self.tree.delete(*self.tree.get_children())
def copy_points_result(self):
"""复制待测点结果"""
try:
result = self.points_text.get("1.0", tk.END)
self.parent.clipboard_clear()
self.parent.clipboard_append(result)
messagebox.showinfo("复制成功", "结果已复制到剪贴板")
except Exception as e:
messagebox.showerror("复制失败", str(e))
def clear_points_results(self):
"""清空待测点结果"""
self.points_text.config(state='normal')
self.points_text.delete(1.0, tk.END)
self.points_text.config(state='disabled')
# === 主窗口 ===
class MainApp:
def __init__(self, root):
self.root = root
self.root.title("工程计算工具箱")
self.root.geometry("1200x800")
self.setup_ui()
def setup_ui(self):
# 创建选项卡
self.notebook = ttk.Notebook(self.root)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 桥梁计算
bridge_frame = ttk.Frame(self.notebook)
self.notebook.add(bridge_frame, text="桥梁计算")
self.bridge_calculator = BridgeCalculatorApp(bridge_frame)
# 三角高程
survey_frame = ttk.Frame(self.notebook)
self.notebook.add(survey_frame, text="三角高程")
self.survey_calculator = SurveyCalculator(survey_frame)
# 角度、曲率
combined_frame = ttk.Frame(self.notebook)
self.notebook.add(combined_frame, text="角度、曲率")
self.combined_calculator = CombinedCalculator(combined_frame)
# 隧道计算
tunnel_frame = ttk.Frame(self.notebook)
self.notebook.add(tunnel_frame, text="隧道计算")
self.tunnel_calculator = CompactTunnelCalculator(tunnel_frame)
if __name__ == "__main__":
root = tk.Tk()
app = MainApp(root)
root.mainloop()
|
评分
-
查看全部评分
|