391 lines
17 KiB
Python
391 lines
17 KiB
Python
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() |