""" Программа 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, load_table3 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, table3_path: str = ""): """Выполняется в отдельном потоке.""" 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)}") t3_images = {} t3_scales = {} if table3_path and Path(table3_path).is_file(): try: t3_images, t3_scales = load_table3(table3_path) log_callback(f" Таблица 3 (схемы): {len(t3_images)} записей") except Exception as exc: log_callback(f" Предупреждение: Не удалось загрузить Таблицу 3: {exc}") 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: scheme_png = t3_images.get(str(zu_file).strip()) # fallback: если в таблице 3 нет изображения, попробуем найти файл в папке с MIF (mif_dir) if scheme_png is None: found = None mif_root = Path(mif_dir) if mif_root.exists(): for ext in ('.png', '.jpg', '.jpeg'): for p in mif_root.rglob(f'*{str(zu_file).strip()}*'): if p.suffix.lower() == ext: found = p break if found: break if found: try: scheme_png = found.read_bytes() log_callback(f" image loaded from {found}") except Exception: scheme_png = None oks_file_name = str(t2_row[10]).strip() if len(t2_row) > 10 and t2_row[10] else "" oks_scheme_png = t3_images.get(oks_file_name) if oks_file_name else None zu_scale = t3_scales.get(str(zu_file).strip()) oks_scale = t3_scales.get(oks_file_name) if oks_file_name else None loc_zu = str(t2_row[4]).strip() if len(t2_row) > 4 and t2_row[4] else "" fill_form(template_path, str(out_path), ki, t2_row, mif_idx, zu_scheme=scheme_png, oks_scheme=oks_scheme_png, zu_scale=zu_scale, oks_scale=oks_scale) generated.append({"name": filename, "path": str(out_path), "kv": str(kv), "zu_file": str(zu_file), "loc_zu": loc_zu}) 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_t3 = self._add_field(card, "Таблица 3 (схемы):", "xlsx", 2) self.var_mif = self._add_field(card, "Папка MIF:", "dir", 3) self.var_template = self._add_field(card, "Шаблон XLSX:", "xlsx", 4) self.var_output = self._add_field(card, "Папка вывода:", "dir", 5) # Дефолты 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_t3.set("") self.var_mif.set(str(data)) self.var_template.set(str(data / "ШАБЛОН ВЫЯВЛЕННЫЕ-2.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() t3 = self.var_t3.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), kwargs={"table3_path": t3}, 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 со списком выявленных объектов • Таблица 3 — файл xlsx со схемами (необязательно) • Папка MIF — папка с MIF-файлами контуров (*.mif) • Шаблон — файл шаблона XLSX • Папка вывода — куда сохранять результаты Результаты: • Отдельный xlsx для каждой строки Таблицы 2 • ВСЕ_ФОРМЫ.xlsx — все формы на одном листе • Таблица4_Перечень_форм.xlsx — перечень файлов Шрифт всех ячеек: Times New Roman 11 pt. """) # ─────────────────────────────── Точка входа ────────────────────────────── # if __name__ == "__main__": app = App() app.mainloop()