project/frontend.py

391 lines
17 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import tkinter as tk
from tkinter import ttk
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from generation_landwater import WorldGenerator
from generation_heightmap import HeightmapGenerator
from climate_zones_map import ClimateGenerator
from settings import WorldSettings
from map_styler import MapStyler
import threading
import tempfile
import os
from PIL import Image, ImageTk
class WorldGeneratorApp:
def __init__(self, root):
self.root = root
self.settings = WorldSettings()
self._init_variables()
self._setup_ui()
self.root.protocol("WM_DELETE_WINDOW", self._on_close)
def _init_variables(self):
"""Инициализация переменных"""
self.width_var = tk.IntVar(value=self.settings.width)
self.height_var = tk.IntVar(value=self.settings.height)
self.mode_var = tk.StringVar(value=self.settings.mode)
# Параметры рельефа
self.terrain_rough_var = tk.DoubleVar(value=self.settings.terrain_roughness)
self.continent_size_var = tk.DoubleVar(value=self.settings.continent_size)
self.continent_rough_var = tk.DoubleVar(value=self.settings.continent_roughness)
self.islands_dens_var = tk.DoubleVar(value=self.settings.islands_density)
self.islands_rough_var = tk.DoubleVar(value=self.settings.islands_roughness)
self.island_size_var = tk.DoubleVar(value=self.settings.island_size)
# Биомы
self.biome_vars = {
'ice': tk.BooleanVar(value=True),
'tundra': tk.BooleanVar(value=True),
'taiga': tk.BooleanVar(value=True),
'forest': tk.BooleanVar(value=True),
'desert': tk.BooleanVar(value=True),
'steppe': tk.BooleanVar(value=True),
'swamp': tk.BooleanVar(value=True),
'jungle': tk.BooleanVar(value=True),
'savanna': tk.BooleanVar(value=True)
}
# Данные карт
self.height_map = None
self.biome_map = None
self.map_window = None
self.canvas = None
def _setup_ui(self):
"""Настройка интерфейса"""
self.root.title("Генератор мира")
self.root.geometry("900x650")
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Панель управления
control_frame = ttk.LabelFrame(main_frame, text="Управление")
control_frame.pack(fill=tk.X, pady=5)
# Первый ряд кнопок
btn_row1 = ttk.Frame(control_frame)
btn_row1.pack(fill=tk.X)
buttons_row1 = [
("Сгенерировать землю", self.generate_land),
("Сгенерировать высоты", self.generate_height),
("Сгенерировать климат", self.generate_climate),
("Показать карту", self.show_map)
]
for i, (text, cmd) in enumerate(buttons_row1):
ttk.Button(btn_row1, text=text, command=cmd).grid(
row=0, column=i, padx=5, pady=5, sticky="ew")
btn_row1.grid_columnconfigure(i, weight=1)
# Второй ряд кнопок
btn_row2 = ttk.Frame(control_frame)
btn_row2.pack(fill=tk.X, pady=5)
buttons_row2 = [
("АВТО", self.auto_generate),
("Стилизовать под свиток", self.style_map),
("Настройки", self.open_settings)
]
for i, (text, cmd) in enumerate(buttons_row2):
ttk.Button(btn_row2, text=text, command=cmd).grid(
row=0, column=i, padx=5, pady=5, sticky="ew")
btn_row2.grid_columnconfigure(i, weight=1)
# Статус бар
self.status_var = tk.StringVar(value="Готов к работе")
ttk.Label(main_frame, textvariable=self.status_var,
relief=tk.SUNKEN, anchor=tk.W).pack(fill=tk.X, pady=5)
def _on_close(self):
"""Корректное закрытие приложения"""
if self.map_window:
self.map_window.destroy()
plt.close('all')
self.root.destroy()
def auto_generate(self):
"""Автоматическая генерация без стилизации"""
def _generate():
try:
self.status_var.set("Автогенерация: создание земли...")
self.generate_land()
self.status_var.set("Автогенерация: создание высот...")
self.generate_height()
self.status_var.set("Автогенерация: создание климата...")
self.generate_climate()
self.status_var.set("Автогенерация завершена!")
self.show_map()
except Exception as e:
self.status_var.set(f"Ошибка: {str(e)}")
threading.Thread(target=_generate, daemon=True).start()
def open_settings(self):
settings_win = tk.Toplevel(self.root)
settings_win.title("Настройки генерации")
settings_win.geometry("600x500")
notebook = ttk.Notebook(settings_win)
# Основные настройки
basic_frame = ttk.Frame(notebook)
self.create_basic_settings(basic_frame)
notebook.add(basic_frame, text="Основные")
# Настройки биомов
biome_frame = ttk.Frame(notebook)
self.create_biome_settings(biome_frame)
notebook.add(biome_frame, text="Биомы")
notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
# Кнопки управления
btn_frame = ttk.Frame(settings_win)
ttk.Button(btn_frame, text="Применить",
command=lambda: [self.save_settings(), settings_win.destroy()]).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="Отмена",
command=settings_win.destroy).pack(side=tk.LEFT, padx=5)
btn_frame.pack(pady=10)
def style_map(self):
"""Стилизация текущей карты под старинный свиток"""
if not hasattr(self, 'biome_map') and not hasattr(self, 'height_map'):
self.status_var.set("Ошибка: нет данных для стилизации")
return
try:
self.status_var.set("Стилизация...")
self.root.update_idletasks()
# Подготовка данных
if hasattr(self, 'biome_map') and self.biome_map is not None:
img_data = (self.biome_map[:, :, :3] * 255).astype(np.uint8)
else:
norm_height = (self.height_map - self.height_map.min()) / (self.height_map.max() - self.height_map.min())
img_data = (plt.cm.terrain(norm_height)[:, :, :3] * 255).astype(np.uint8)
# Создаем PIL Image и применяем стилизацию
pil_img = Image.fromarray(img_data, 'RGB')
styled_img = MapStyler.apply_parchment_effect(pil_img)
# Отображаем результат
self.preview_window = tk.Toplevel(self.root)
self.preview_window.title("Стилизованная карта")
img_tk = ImageTk.PhotoImage(styled_img)
label = ttk.Label(self.preview_window, image=img_tk)
label.image = img_tk
label.pack()
self.status_var.set("Стилизация завершена!")
except Exception as e:
self.status_var.set(f"Ошибка стилизации: {str(e)}")
def create_basic_settings(self, frame):
"""Настройки размера и типа карты"""
# Размер карты
size_frame = ttk.LabelFrame(frame, text="Размер карты")
size_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Label(size_frame, text="Ширина:").grid(row=0, column=0, sticky="e")
ttk.Spinbox(size_frame, from_=100, to=1000, textvariable=self.width_var).grid(
row=0, column=1, sticky="ew", padx=5)
ttk.Label(size_frame, text="Высота:").grid(row=1, column=0, sticky="e")
ttk.Spinbox(size_frame, from_=100, to=1000, textvariable=self.height_var).grid(
row=1, column=1, sticky="ew", padx=5)
# Тип генерации
type_frame = ttk.LabelFrame(frame, text="Тип ландшафта")
type_frame.pack(fill=tk.X, padx=5, pady=5)
types = ["land_only", "continent", "islands"]
texts = ["Только суша", "Материк", "Острова"]
for t, text in zip(types, texts):
ttk.Radiobutton(type_frame, text=text, variable=self.mode_var, value=t).pack(
anchor=tk.W)
# Параметры рельефа
terrain_frame = ttk.LabelFrame(frame, text="Параметры рельефа")
terrain_frame.pack(fill=tk.X, padx=5, pady=5)
params = [
("Шероховатость:", self.terrain_rough_var),
("Размер материка:", self.continent_size_var),
("Шероховатость материка:", self.continent_rough_var),
("Плотность островов:", self.islands_dens_var),
("Шероховатость островов:", self.islands_rough_var),
("Размер островов:", self.island_size_var)
]
for i, (text, var) in enumerate(params):
ttk.Label(terrain_frame, text=text).grid(row=i, column=0, sticky="e", padx=5)
ttk.Scale(terrain_frame, from_=0.1, to=1.0, variable=var,
orient=tk.HORIZONTAL).grid(row=i, column=1, sticky="ew", padx=5)
ttk.Label(terrain_frame, textvariable=var).grid(row=i, column=2, padx=5)
# Режим климата
climate_frame = ttk.LabelFrame(frame, text="Режим климата")
climate_frame.pack(fill=tk.X, padx=5, pady=5)
if not hasattr(self.settings, 'climate_mode'):
self.settings.climate_mode = 'realistic'
ttk.Radiobutton(climate_frame, text="Реалистичный",
variable=self.settings.climate_mode,
value="realistic").pack(anchor=tk.W)
ttk.Radiobutton(climate_frame, text="Художественный",
variable=self.settings.climate_mode,
value="artistic").pack(anchor=tk.W)
def create_biome_settings(self, frame):
"""Настройки биомов через чекбоксы"""
biome_frame = ttk.LabelFrame(frame, text="Выбор биомов")
biome_frame.pack(fill=tk.BOTH, padx=5, pady=5, expand=True)
for i, (biome, var) in enumerate(self.biome_vars.items()):
col = i % 3
row = i // 3
ttk.Checkbutton(
biome_frame,
text=biome.capitalize(),
variable=var
).grid(row=row, column=col, sticky="w", padx=5, pady=2)
def save_settings(self):
"""Сохраняет все настройки"""
try:
self.settings.width = self.width_var.get()
self.settings.height = self.height_var.get()
self.settings.mode = self.mode_var.get()
self.settings.terrain_roughness = self.terrain_rough_var.get()
self.settings.continent_size = self.continent_size_var.get()
self.settings.continent_roughness = self.continent_rough_var.get()
self.settings.islands_density = self.islands_dens_var.get()
self.settings.islands_roughness = self.islands_rough_var.get()
self.settings.island_size = self.island_size_var.get()
self.status_var.set("Настройки сохранены")
except Exception as e:
self.status_var.set(f"Ошибка сохранения: {str(e)}")
def generate_land(self):
"""Генерация карты земли/воды"""
try:
self.land_generator = WorldGenerator(
width=self.settings.width,
height=self.settings.height,
seed=self.settings.get_valid_seed()
)
if self.settings.mode == 'land_only':
self.height_map = self.land_generator.generate_land_only()
elif self.settings.mode == 'continent':
self.height_map = self.land_generator.generate_continent(
size=self.settings.continent_size,
roughness=self.settings.continent_roughness
)
else:
self.height_map = self.land_generator.generate_islands(
density=self.settings.islands_density,
island_size=self.settings.island_size,
roughness=self.settings.islands_roughness
)
self.biome_map = None
self.styled_map_path = None
self.status_var.set("Карта земли сгенерирована")
except Exception as e:
self.status_var.set(f"Ошибка генерации земли: {str(e)}")
def generate_height(self):
"""Генерация карты высот"""
if not hasattr(self, 'height_map') or self.height_map is None:
self.status_var.set("Ошибка: сначала сгенерируйте карту земли")
return
try:
self.height_generator = HeightmapGenerator(
width=self.settings.width,
height=self.settings.height,
seed=self.settings.get_valid_seed()
)
self.height_map = self.height_generator.generate_heightmap(
self.height_map,
roughness=self.settings.terrain_roughness
)
self.biome_map = None
self.styled_map_path = None
self.status_var.set("Карта высот сгенерирована")
except Exception as e:
self.status_var.set(f"Ошибка генерации высот: {str(e)}")
def generate_climate(self):
if not hasattr(self, 'height_map'):
self.status_var.set("Ошибка: сначала сгенерируйте карту высот")
return
try:
enabled_biomes = [b for b, var in self.biome_vars.items() if var.get()]
if self.settings.climate_mode == 'realistic':
from climate_zones_map import ClimateGenerator
self.climate_gen = ClimateGenerator(self.height_map, self.settings)
else:
from climate_zones_artistic import ArtisticClimateGenerator
self.climate_gen = ArtisticClimateGenerator(self.height_map, self.settings)
self.biome_map = self.climate_gen.generate_biome_map(['water'] + enabled_biomes)
self.status_var.set(f"Карта климата ({self.settings.climate_mode}) сгенерирована")
except Exception as e:
self.status_var.set(f"Ошибка генерации климата: {str(e)}")
def show_map(self):
"""Отображение карты во встроенном окне"""
if not hasattr(self, 'biome_map') and not hasattr(self, 'height_map'):
self.status_var.set("Нет данных для отображения")
return
if self.map_window:
self.map_window.destroy()
self.map_window = tk.Toplevel(self.root)
self.map_window.title("Просмотр карты")
fig = plt.Figure(figsize=(8, 6))
ax = fig.add_subplot(111)
if hasattr(self, 'biome_map') and self.biome_map is not None:
ax.imshow(self.biome_map)
ax.set_title("Карта биомов")
else:
ax.imshow(self.height_map, cmap='terrain')
ax.set_title("Карта высот")
ax.axis('off')
self.canvas = FigureCanvasTkAgg(fig, master=self.map_window)
self.canvas.draw()
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# Кнопка закрытия
ttk.Button(self.map_window, text="Закрыть",
command=self.map_window.destroy).pack(pady=5)
if __name__ == "__main__":
root = tk.Tk()
app = WorldGeneratorApp(root)
root.mainloop()