This commit is contained in:
2026-03-10 20:21:31 +05:00
commit ec82b528a3
14 changed files with 1041 additions and 0 deletions

284
main.py Normal file
View File

@@ -0,0 +1,284 @@
"""
Программа 17: Заполнение формы по выявленным-1
Графический интерфейс (tkinter) + запуск обработки.
"""
import sys
import threading
import traceback
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from pathlib import Path
from services.data_loader import load_ki, load_table2, build_mif_index
from services.form_filler import fill_form
from services.output_generator import make_filename, combine_forms, generate_table4
# ─────────────────────────────── Цвета / стиль ──────────────────────────── #
BG = "#1C3557" # тёмно-синий фон
BG_FRAME = "#243F6B" # фон панелей
FG = "#FFFFFF" # белый текст
FG_DIM = "#A8C4E0" # приглушённый текст
BTN_RUN = "#2E7D32" # зелёная кнопка «Запустить»
BTN_HELP = "#1565C0" # синяя кнопка «Справка»
FONT = ("Arial", 11)
FONT_H = ("Arial", 13, "bold")
FONT_LOG = ("Courier New", 9)
# ─────────────────────────────── Основная логика ────────────────────────── #
def run_processing(table1_path: str, table2_path: str, mif_dir: str,
template_path: str, output_dir: str,
log_callback, progress_callback, done_callback):
"""Выполняется в отдельном потоке."""
try:
out = Path(output_dir)
out.mkdir(parents=True, exist_ok=True)
log_callback("[1/4] Загрузка исходных данных...")
progress_callback(10)
ki = load_ki(table1_path)
t2_rows = load_table2(table2_path)
mif_idx = build_mif_index(mif_dir)
log_callback(f" КИ: {next(iter(ki.values()), '?')}")
log_callback(f" Таблица 2: {len(t2_rows)} строк")
log_callback(f" MIF-файлов: {len(mif_idx)}")
log_callback("\n[2/4] Генерация форм...")
generated, errors = [], []
total = sum(1 for r in t2_rows if r[0])
for i, t2_row in enumerate(t2_rows, 1):
zu_file = t2_row[0] if t2_row[0] else ""
kv = t2_row[1] if t2_row[1] else ""
if not zu_file:
continue
filename = make_filename(str(kv), str(zu_file))
out_path = out / filename
log_callback(f" [{len(generated)+len(errors)+1}/{total}] {filename} ...", end="")
try:
fill_form(template_path, str(out_path), ki, t2_row, mif_idx)
generated.append({"name": filename, "path": str(out_path),
"kv": str(kv), "zu_file": str(zu_file)})
log_callback(" OK")
except Exception as exc:
log_callback(f" ОШИБКА: {exc}")
errors.append((filename, str(exc)))
progress_callback(10 + int(70 * (len(generated) + len(errors)) / max(total, 1)))
log_callback(f"\n Создано: {len(generated)}, ошибок: {len(errors)}")
if generated:
log_callback("\n[3/4] Создание объединённого файла...")
progress_callback(82)
combined = out / "ВСЕОРМЫ.xlsx"
try:
combine_forms([r["path"] for r in generated], str(combined))
log_callback(f"{combined}")
except Exception as exc:
log_callback(f" ОШИБКА объединения: {exc}")
log_callback("\n[4/4] Создание Таблицы 4...")
progress_callback(95)
t4 = out / "Таблица4_Перечень_форм.xlsx"
generate_table4(generated, str(t4))
log_callback(f"{t4}")
progress_callback(100)
done_callback(generated, errors, str(out))
except Exception as exc:
log_callback(f"\nКРИТИЧЕСКАЯ ОШИБКА:\n{traceback.format_exc()}")
done_callback([], [(str(exc), str(exc))], output_dir)
# ─────────────────────────────── GUI ────────────────────────────────────── #
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Программа 17 — Выявленные-1")
self.geometry("700x600")
self.minsize(600, 550)
self.configure(bg=BG)
self._build_ui()
# ── Построение интерфейса ────────────────────────────────────────────── #
def _build_ui(self):
# Заголовок
tk.Label(self, text="Программа 17: Заполнение формы по Выявленным-1",
font=FONT_H, bg=BG, fg=FG).pack(pady=(14, 8))
# Карточка с полями ввода
card = tk.Frame(self, bg=BG_FRAME, bd=0)
card.pack(fill=tk.X, padx=18, pady=4)
self.var_t1 = self._add_field(card, "Таблица 1 (КИ):", "xlsx", 0)
self.var_t2 = self._add_field(card, "Таблица 2 (данные):", "xlsx", 1)
self.var_mif = self._add_field(card, "Папка MIF:", "dir", 2)
self.var_template = self._add_field(card, "Шаблон XLSX:", "xlsx", 3)
self.var_output = self._add_field(card, "Папка вывода:", "dir", 4)
# Дефолты
base = Path(__file__).parent
data = base / "data" / "Исх.данные"
self.var_t1.set(str(data / "Таблица1 КИ-Неустроева.xlsx"))
self.var_t2.set(str(data / "Таблица 2 Перечень выявленных исх.данные.xlsx"))
self.var_mif.set(str(data / "1. 74 34 0400007 ГК вымпел-73"))
self.var_template.set(str(base / "data" / "ШАБЛОН ВЫЯВЛЕННЫЕ-1.xlsx"))
self.var_output.set(str(base / "output"))
# Прогресс-бар
pf = tk.Frame(self, bg=BG)
pf.pack(fill=tk.X, padx=18, pady=(10, 2))
self.progress = ttk.Progressbar(pf, length=600, mode="determinate")
self.progress.pack(fill=tk.X)
self.progress_label = tk.Label(pf, text="Готов к работе",
font=("Arial", 9), bg=BG, fg=FG_DIM)
self.progress_label.pack(anchor="w")
# Лог
lf = tk.Frame(self, bg=BG)
lf.pack(fill=tk.BOTH, expand=True, padx=18, pady=(4, 4))
tk.Label(lf, text="Журнал:", font=("Arial", 9), bg=BG, fg=FG_DIM).pack(anchor="w")
self.log_text = tk.Text(lf, font=FONT_LOG, bg="#0D1F33", fg="#C8E6FF",
height=10, state=tk.DISABLED, wrap=tk.WORD)
sb = tk.Scrollbar(lf, command=self.log_text.yview)
self.log_text.configure(yscrollcommand=sb.set)
sb.pack(side=tk.RIGHT, fill=tk.Y)
self.log_text.pack(fill=tk.BOTH, expand=True)
# Кнопки
bf = tk.Frame(self, bg=BG)
bf.pack(fill=tk.X, padx=18, pady=(6, 12))
tk.Button(bf, text="Справка", command=self._show_help,
font=FONT, bg=BTN_HELP, fg=FG,
padx=14, pady=8, relief=tk.FLAT).pack(side=tk.LEFT)
self.btn_run = tk.Button(bf, text="▶ Запустить",
command=self._start, font=FONT_H,
bg=BTN_RUN, fg=FG, padx=20, pady=8, relief=tk.FLAT)
self.btn_run.pack(side=tk.RIGHT)
def _add_field(self, parent, label: str, kind: str, row_idx: int):
"""Добавить строку: метка + поле + кнопка «...»."""
var = tk.StringVar()
tk.Label(parent, text=label, font=FONT, bg=BG_FRAME, fg=FG,
anchor="w").grid(row=row_idx * 2, column=0, columnspan=3,
sticky="w", padx=12, pady=(8, 0))
entry = tk.Entry(parent, textvariable=var, font=FONT, width=55)
entry.grid(row=row_idx * 2 + 1, column=0, padx=(12, 4), pady=(0, 2), sticky="ew")
btn = tk.Button(parent, text="", font=FONT, width=3,
command=lambda k=kind, v=var: self._browse(k, v))
btn.grid(row=row_idx * 2 + 1, column=1, padx=(0, 12), pady=(0, 2))
parent.columnconfigure(0, weight=1)
return var
def _browse(self, kind: str, var: tk.StringVar):
if kind == "dir":
path = filedialog.askdirectory(title="Выберите папку")
else:
path = filedialog.askopenfilename(
title="Выберите файл",
filetypes=[("Excel файлы", "*.xlsx *.xls"), ("Все файлы", "*.*")]
)
if path:
var.set(path)
# ── Запуск ──────────────────────────────────────────────────────────── #
def _start(self):
t1 = self.var_t1.get().strip()
t2 = self.var_t2.get().strip()
mif = self.var_mif.get().strip()
tmpl = self.var_template.get().strip()
out = self.var_output.get().strip()
missing = [name for name, v in [("Таблица 1", t1), ("Таблица 2", t2),
("Папка MIF", mif), ("Шаблон", tmpl),
("Папка вывода", out)] if not v]
if missing:
messagebox.showerror("Ошибка", "Не заполнены поля:\n" + "\n".join(missing))
return
self._log_clear()
self._set_progress(0, "Запуск...")
self.btn_run.config(state=tk.DISABLED)
thread = threading.Thread(
target=run_processing,
args=(t1, t2, mif, tmpl, out,
self._log_append,
lambda v: self.after(0, self._set_progress, v, None),
self._on_done),
daemon=True,
)
thread.start()
# ── Обратные вызовы из потока ────────────────────────────────────────── #
def _log_append(self, text: str, end: str = "\n"):
def _do():
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, text + end)
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
self.after(0, _do)
def _log_clear(self):
self.log_text.config(state=tk.NORMAL)
self.log_text.delete("1.0", tk.END)
self.log_text.config(state=tk.DISABLED)
def _set_progress(self, value: int, label_text=None):
self.progress["value"] = value
if label_text is not None:
self.progress_label.config(text=label_text)
elif value == 100:
self.progress_label.config(text="Готово!")
def _on_done(self, generated: list, errors: list, out_dir: str):
def _do():
self.btn_run.config(state=tk.NORMAL)
if errors and not generated:
messagebox.showerror("Ошибка", f"Обработка завершилась с ошибками:\n{errors[0][1]}")
return
msg = (
f"Обработка завершена!\n\n"
f"Создано форм: {len(generated)}\n"
f"Ошибок: {len(errors)}\n\n"
f"Результаты в папке:\n{out_dir}"
)
if errors:
msg += f"\n\nФайлы с ошибками ({len(errors)}):\n" + \
"\n".join(f"{n}" for n, _ in errors[:10])
messagebox.showinfo("Готово", msg)
self.after(0, _do)
# ── Справка ─────────────────────────────────────────────────────────── #
def _show_help(self):
messagebox.showinfo("Справка", """\
Программа 17: Заполнение формы по Выявленным-1
Поля ввода:
• Таблица 1 — файл xlsx с данными кадастрового инженера
• Таблица 2 — файл xlsx со списком выявленных объектов
• Папка MIF — папка с MIF-файлами контуров (*.mif)
• Шаблон — файл шаблона XLSX
• Папка вывода — куда сохранять результаты
Результаты:
• Отдельный xlsx для каждой строки Таблицы 2
ВСЕОРМЫ.xlsx — все формы на одном листе
Таблица4_Перечень_форм.xlsx — перечень файлов
Шрифт всех ячеек: Times New Roman 11 pt.
""")
# ─────────────────────────────── Точка входа ────────────────────────────── #
if __name__ == "__main__":
app = App()
app.mainloop()