import tkinter as tk from tkinter import ttk, messagebox, filedialog import json from datetime import datetime import math class AdvancedSlabCalculator: def __init__(self, root): self.root = root self.root.title("Продвинутый калькулятор монолитной плиты") self.root.geometry("900x700") # Создаем вкладки self.notebook = ttk.Notebook(root) self.notebook.pack(pady=10, fill='both', expand=True) # Вкладка основного расчета self.calc_tab = ttk.Frame(self.notebook) self.notebook.add(self.calc_tab, text="Основной расчет") # Вкладка сохраненных расчетов self.history_tab = ttk.Frame(self.notebook) self.notebook.add(self.history_tab, text="История расчетов") # Инициализация вкладок self.setup_calc_tab() self.setup_history_tab() # Данные для расчетов self.materials = { 'concrete': [ {"name": "М100 (В7.5)", "price": 3800, "delivery": 15000}, {"name": "М200 (В15)", "price": 4200, "delivery": 15000}, {"name": "М250 (В20)", "price": 4500, "delivery": 15000}, {"name": "М300 (В22.5)", "price": 4800, "delivery": 15000}, {"name": "М350 (В25)", "price": 5200, "delivery": 18000} ], 'rebar_types': [ {"name": "Каркас простой (сетка)", "kg_per_m3": 80, "price": 60}, {"name": "Каркас усиленный", "kg_per_m3": 120, "price": 65}, {"name": "Пространственный каркас", "kg_per_m3": 150, "price": 70} ], 'labor': { 'preparation': 500, # руб/м2 подготовка 'formwork': 800, # руб/м2 опалубка 'reinforcement': 300, # руб/кг арматура 'concreting': 1200 # руб/м3 бетонирование } } # Переменные для хранения данных self.calc_history = [] self.load_history() def setup_calc_tab(self): # Основные параметры плиты slab_frame = ttk.LabelFrame(self.calc_tab, text="Параметры плиты", padding=10) slab_frame.pack(fill='x', padx=10, pady=5) ttk.Label(slab_frame, text="Длина (м):").grid(row=0, column=0, sticky='e') self.length_entry = ttk.Entry(slab_frame) self.length_entry.grid(row=0, column=1, padx=5, pady=2) self.length_entry.insert(0, "10.0") ttk.Label(slab_frame, text="Ширина (м):").grid(row=1, column=0, sticky='e') self.width_entry = ttk.Entry(slab_frame) self.width_entry.grid(row=1, column=1, padx=5, pady=2) self.width_entry.insert(0, "10.0") ttk.Label(slab_frame, text="Толщина (м):").grid(row=2, column=0, sticky='e') self.thickness_entry = ttk.Entry(slab_frame) self.thickness_entry.grid(row=2, column=1, padx=5, pady=2) self.thickness_entry.insert(0, "0.3") # Параметры материалов materials_frame = ttk.LabelFrame(self.calc_tab, text="Материалы", padding=10) materials_frame.pack(fill='x', padx=10, pady=5) ttk.Label(materials_frame, text="Марка бетона:").grid(row=0, column=0, sticky='e') self.concrete_combo = ttk.Combobox(materials_frame, values=[c['name'] for c in self.materials['concrete']) self.concrete_combo.grid(row=0, column=1, padx=5, pady=2, sticky='ew') self.concrete_combo.current(2) ttk.Label(materials_frame, text="Тип арматурного каркаса:").grid(row=1, column=0, sticky='e') self.rebar_combo = ttk.Combobox(materials_frame, values=[r['name'] for r in self.materials['rebar_types']]) self.rebar_combo.grid(row=1, column=1, padx=5, pady=2, sticky='ew') self.rebar_combo.current(1) ttk.Label(materials_frame, text="Расстояние доставки (км):").grid(row=2, column=0, sticky='e') self.distance_entry = ttk.Entry(materials_frame) self.distance_entry.grid(row=2, column=1, padx=5, pady=2) self.distance_entry.insert(0, "25") # Кнопки расчета button_frame = ttk.Frame(self.calc_tab) button_frame.pack(fill='x', padx=10, pady=10) ttk.Button(button_frame, text="Рассчитать", command=self.calculate).pack(side='left', padx=5) ttk.Button(button_frame, text="Сохранить расчет", command=self.save_calculation).pack(side='left', padx=5) ttk.Button(button_frame, text="Очистить", command=self.clear_fields).pack(side='left', padx=5) # Результаты расчета self.results_frame = ttk.LabelFrame(self.calc_tab, text="Результаты расчета", padding=10) self.results_frame.pack(fill='both', expand=True, padx=10, pady=5) # Создаем Treeview для отображения результатов self.results_tree = ttk.Treeview(self.results_frame, columns=('parameter', 'value', 'unit'), show='headings') self.results_tree.heading('parameter', text='Параметр') self.results_tree.heading('value', text='Значение') self.results_tree.heading('unit', text='Ед. изм.') self.results_tree.column('parameter', width=250) self.results_tree.column('value', width=150) self.results_tree.column('unit', width=100) self.results_tree.pack(fill='both', expand=True) # Добавляем полосу прокрутки scrollbar = ttk.Scrollbar(self.results_tree, orient="vertical", command=self.results_tree.yview) scrollbar.pack(side='right', fill='y') self.results_tree.configure(yscrollcommand=scrollbar.set) def setup_history_tab(self): # Treeview для истории расчетов self.history_tree = ttk.Treeview(self.history_tab, columns=('date', 'size', 'concrete', 'total'), show='headings') self.history_tree.heading('date', text='Дата') self.history_tree.heading('size', text='Размеры плиты') self.history_tree.heading('concrete', text='Бетон') self.history_tree.heading('total', text='Общая стоимость') self.history_tree.column('date', width=150) self.history_tree.column('size', width=150) self.history_tree.column('concrete', width=200) self.history_tree.column('total', width=150) self.history_tree.pack(fill='both', expand=True, padx=10, pady=10) # Кнопки для работы с историей button_frame = ttk.Frame(self.history_tab) button_frame.pack(fill='x', padx=10, pady=10) ttk.Button(button_frame, text="Загрузить расчет", command=self.load_selected_calculation).pack(side='left', padx=5) ttk.Button(button_frame, text="Удалить запись", command=self.delete_history_entry).pack(side='left', padx=5) ttk.Button(button_frame, text="Экспорт в JSON", command=self.export_history).pack(side='left', padx=5) ttk.Button(button_frame, text="Очистить историю", command=self.clear_history).pack(side='left', padx=5) def calculate(self): try: # Получаем входные данные length = float(self.length_entry.get()) width = float(self.width_entry.get()) thickness = float(self.thickness_entry.get()) distance = float(self.distance_entry.get()) # Получаем выбранные материалы concrete = self.materials['concrete'][self.concrete_combo.current()] rebar_type = self.materials['rebar_types'][self.rebar_combo.current()] # Основные расчеты slab_area = length * width concrete_volume = slab_area * thickness rebar_weight = concrete_volume * rebar_type['kg_per_m3'] formwork_area = 2 * (length + width) * thickness # Расчет стоимости материалов concrete_cost = concrete_volume * concrete['price'] rebar_cost = rebar_weight * rebar_type['price'] # Расчет доставки бетона delivery_cost = concrete['delivery'] * (1 + distance / 50) # Увеличиваем стоимость на 1% за каждый км свыше 50 # Расчет трудозатрат labor_cost = ( slab_area * self.materials['labor']['preparation'] + formwork_area * self.materials['labor']['formwork'] + rebar_weight * self.materials['labor']['reinforcement'] + concrete_volume * self.materials['labor']['concreting'] ) # Общая стоимость total_cost = concrete_cost + rebar_cost + delivery_cost + labor_cost # Очищаем предыдущие результаты for item in self.results_tree.get_children(): self.results_tree.delete(item) # Добавляем новые результаты results = [ ("Размеры плиты", f"{length}x{width}x{thickness}", "м"), ("Площадь плиты", round(slab_area, 2), "м²"), ("Объем бетона", round(concrete_volume, 2), "м³"), ("Марка бетона", concrete['name'], ""), ("Тип арматурного каркаса", rebar_type['name'], ""), ("Вес арматуры", round(rebar_weight, 2), "кг"), ("Площадь опалубки", round(formwork_area, 2), "м²"), ("", "", ""), ("СТОИМОСТЬ МАТЕРИАЛОВ", "", ""), ("Бетон", f"{round(concrete_cost, 2)}", "руб"), ("Арматура", f"{round(rebar_cost, 2)}", "руб"), ("Доставка бетона", f"{round(delivery_cost, 2)}", "руб"), ("", "", ""), ("ТРУДОЗАТРАТЫ", "", ""), ("Подготовка основания", f"{round(slab_area * self.materials['labor']['preparation'], 2)}", "руб"), ("Опалубочные работы", f"{round(formwork_area * self.materials['labor']['formwork'], 2)}", "руб"), ("Арматурные работы", f"{round(rebar_weight * self.materials['labor']['reinforcement'], 2)}", "руб"), ("Бетонирование", f"{round(concrete_volume * self.materials['labor']['concreting'], 2)}", "руб"), ("Итого трудозатраты", f"{round(labor_cost, 2)}", "руб"), ("", "", ""), ("ОБЩАЯ СТОИМОСТЬ", f"{round(total_cost, 2)}", "руб") ] for result in results: self.results_tree.insert('', 'end', values=result) # Сохраняем данные для возможного сохранения self.current_calculation = { 'date': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'parameters': { 'length': length, 'width': width, 'thickness': thickness, 'concrete': concrete['name'], 'rebar_type': rebar_type['name'], 'distance': distance }, 'results': { 'concrete_volume': concrete_volume, 'rebar_weight': rebar_weight, 'formwork_area': formwork_area, 'concrete_cost': concrete_cost, 'rebar_cost': rebar_cost, 'delivery_cost': delivery_cost, 'labor_cost': labor_cost, 'total_cost': total_cost } } except ValueError as e: messagebox.showerror("Ошибка", "Пожалуйста, проверьте введенные данные") def save_calculation(self): if hasattr(self, 'current_calculation'): self.calc_history.append(self.current_calculation) self.update_history_tree() self.save_history() messagebox.showinfo("Сохранено", "Расчет успешно сохранен в историю") else: messagebox.showwarning("Ошибка", "Сначала выполните расчет") def clear_fields(self): self.length_entry.delete(0, 'end') self.width_entry.delete(0, 'end') self.thickness_entry.delete(0, 'end') self.distance_entry.delete(0, 'end') self.length_entry.insert(0, "10.0") self.width_entry.insert(0, "10.0") self.thickness_entry.insert(0, "0.3") self.distance_entry.insert(0, "25") self.concrete_combo.current(2) self.rebar_combo.current(1) for item in self.results_tree.get_children(): self.results_tree.delete(item) def update_history_tree(self): for item in self.history_tree.get_children(): self.history_tree.delete(item) for calc in self.calc_history: params = calc['parameters'] self.history_tree.insert('', 'end', values=( calc['date'], f"{params['length']}x{params['width']}x{params['thickness']}", params['concrete'], f"{round(calc['results']['total_cost'], 2)} руб" )) def load_selected_calculation(self): selected_item = self.history_tree.selection() if not selected_item: messagebox.showwarning("Ошибка", "Выберите расчет из списка") return item_data = self.history_tree.item(selected_item) date = item_data['values'][0] for calc in self.calc_history: if calc['date'] == date: # Заполняем поля параметров self.clear_fields() params = calc['parameters'] self.length_entry.delete(0, 'end') self.length_entry.insert(0, str(params['length'])) self.width_entry.delete(0, 'end') self.width_entry.insert(0, str(params['width'])) self.thickness_entry.delete(0, 'end') self.thickness_entry.insert(0, str(params['thickness'])) self.distance_entry.delete(0, 'end') self.distance_entry.insert(0, str(params['distance'])) # Устанавливаем комбобоксы for i, concrete in enumerate(self.materials['concrete']): if concrete['name'] == params['concrete']: self.concrete_combo.current(i) break for i, rebar in enumerate(self.materials['rebar_types']): if rebar['name'] == params['rebar_type']: self.rebar_combo.current(i) break # Выполняем расчет self.calculate() break def delete_history_entry(self): selected_item = self.history_tree.selection() if not selected_item: messagebox.showwarning("Ошибка", "Выберите расчет для удаления") return item_data = self.history_tree.item(selected_item) date = item_data['values'][0] self.calc_history = [calc for calc in self.calc_history if calc['date'] != date] self.update_history_tree() self.save_history() messagebox.showinfo("Удалено", "Запись удалена из истории") def clear_history(self): if messagebox.askyesno("Подтверждение", "Очистить всю историю расчетов?"): self.calc_history = [] self.update_history_tree() self.save_history() def export_history(self): file_path = filedialog.asksaveasfilename( defaultextension=".json", filetypes=[("JSON files", "*.json"), ("All files", "*.*")], title="Сохранить историю расчетов как" ) if file_path: try: with open(file_path, 'w', encoding='utf-8') as f: json.dump(self.calc_history, f, ensure_ascii=False, indent=4) messagebox.showinfo("Успех", "История расчетов экспортирована") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось сохранить файл: {str(e)}") def save_history(self): try: with open('slab_calculator_history.json', 'w', encoding='utf-8') as f: json.dump(self.calc_history, f, ensure_ascii=False, indent=4) except Exception as e: print(f"Ошибка сохранения истории: {str(e)}") def load_history(self): try: with open('slab_calculator_history.json', 'r', encoding='utf-8') as f: self.calc_history = json.load(f) self.update_history_tree() except FileNotFoundError: self.calc_history = [] except Exception as e: messagebox.showerror("Ошибка", f"Не удалось загрузить историю: {str(e)}") self.calc_history = [] if __name__ == "__main__": root = tk.Tk() app = AdvancedSlabCalculator(root) root.mainloop()