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()