明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 343|回复: 8

python写的计算工具

  [复制链接]
发表于 前天 10:16 | 显示全部楼层 |阅读模式
# ===== 工程计算工具箱.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&#178; + 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&#178;/Ls")
                raise ValueError(f"参数不合法: A&#178; - 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()

评分

参与人数 1明经币 +1 收起 理由
仲文玉 + 1 赞一个!

查看全部评分

回复

使用道具 举报

 楼主| 发表于 前天 10:23 | 显示全部楼层
成品如下,有用的请自行下载

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
回复 支持 反对

使用道具 举报

发表于 前天 10:29 | 显示全部楼层
楼主这是整了个大工程,厉害
回复 支持 反对

使用道具 举报

发表于 前天 10:46 | 显示全部楼层
cjf160204 发表于 2025-4-27 10:23
成品如下,有用的请自行下载

成品是编译过的,源码上面 运行 还不行
回复 支持 反对

使用道具 举报

发表于 前天 12:44 | 显示全部楼层
谢谢楼主分享
回复 支持 反对

使用道具 举报

发表于 前天 17:38 | 显示全部楼层
本帖最后由 vitalgg 于 2025-4-27 17:40 编辑
123523058 发表于 2025-4-27 10:46
成品是编译过的,源码上面 运行 还不行

源码可以运行,就是需要先安装 python。如果是win10以上系统 可以运行以下命令安装
按下 Win+R .执行以下命令,或打开cmd 或 powershell . 在其上运行以下命令。
  1. winget  install python.python3.13
复制代码

回复 支持 反对

使用道具 举报

 楼主| 发表于 前天 18:39 | 显示全部楼层
vitalgg 发表于 2025-4-27 17:38
源码可以运行,就是需要先安装 python。如果是win10以上系统 可以运行以下命令安装
按下 Win+R .执行以 ...

专业,安装python就能用,成品直接就能用
回复 支持 反对

使用道具 举报

发表于 昨天 09:42 | 显示全部楼层
感谢大佬,学习学习
回复 支持 反对

使用道具 举报

发表于 昨天 17:31 | 显示全部楼层

感谢大佬,学习学习
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2025-4-29 00:49 , Processed in 0.259804 second(s), 24 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表