Files
kadastr-17/main.py
2026-03-10 20:21:31 +05:00

285 lines
13 KiB
Python
Raw 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.
"""
Программа 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()