Files
kadastr-17/main.py
2026-03-25 13:38:54 +05:00

327 lines
16 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.
"""
Программа 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()