#!/usr/bin/env python import cv2 import numpy as np import pygetwindow as gw import tkinter as tk from tkinter import messagebox, filedialog, simpledialog from PIL import Image, ImageGrab, ImageTk import threading import time import pickle import os from concurrent.futures import ThreadPoolExecutor import json class TemplatePresetManager: """Gestionnaire de presets de templates avec sauvegarde/chargement""" def __init__(self, presets_file="lastwar_presets.json"): self.presets_file = presets_file self.presets = {} self.current_preset = None self.load_presets() def load_presets(self): try: if os.path.exists(self.presets_file): with open(self.presets_file, 'r', encoding='utf-8') as f: preset_list = json.load(f) self.presets = {} for preset_info in preset_list: name = preset_info['name'] pkl_file = preset_info['file'] if os.path.exists(pkl_file): with open(pkl_file, 'rb') as pf: templates = pickle.load(pf) self.presets[name] = { 'templates': templates, 'file': pkl_file } print(f"Presets charges: {len(self.presets)}") return True except Exception as e: print(f"Erreur chargement presets: {e}") return False def save_presets(self): try: preset_list = [] for name, data in self.presets.items(): preset_list.append({ 'name': name, 'file': data['file'] }) with open(self.presets_file, 'w', encoding='utf-8') as f: json.dump(preset_list, f, indent=2, ensure_ascii=False) return True except Exception as e: print(f"Erreur sauvegarde presets: {e}") return False def add_preset(self, name, templates): safe_name = "".join(c if c.isalnum() or c in (' ', '_', '-') else '_' for c in name) pkl_file = f"preset_{safe_name}.pkl" try: with open(pkl_file, 'wb') as f: pickle.dump(templates, f) self.presets[name] = { 'templates': templates, 'file': pkl_file } self.save_presets() print(f"Preset '{name}' cree avec {len(templates)} templates") return True except Exception as e: print(f"Erreur creation preset: {e}") return False def delete_preset(self, name): if name in self.presets: try: pkl_file = self.presets[name]['file'] if os.path.exists(pkl_file): os.remove(pkl_file) del self.presets[name] self.save_presets() print(f"Preset '{name}' supprime") return True except Exception as e: print(f"Erreur suppression preset: {e}") return False def rename_preset(self, old_name, new_name): if old_name in self.presets and new_name not in self.presets: self.presets[new_name] = self.presets.pop(old_name) self.save_presets() print(f"Preset renomme: '{old_name}' -> '{new_name}'") return True return False def get_preset_names(self): return list(self.presets.keys()) def load_preset(self, name): if name in self.presets: self.current_preset = name return self.presets[name]['templates'] return None class GameOverlay: def __init__(self, window_title="Last War"): self.window_title = window_title self.target_window = None self.running = False self.overlay_root = None self.canvas = None self.overlay_created = False self.preset_manager = TemplatePresetManager() self.captured_templates = [] self.mode = "setup" self.settings_file = "lastwar_settings.pkl" self.current_threshold = 0.85 self.threshold_step = 0.02 self.min_threshold = 0.70 self.max_threshold = 0.98 self.capture_window = None self.capture_canvas = None self.control_window = None self.main_menu = None self.template_mode = "normal" self.debug_matches = [] self.last_positions = [] self.frame_skip = 0 self.max_frame_skip = 1 self.executor = ThreadPoolExecutor(max_workers=3) # ========== MENU PRINCIPAL ========== def create_main_menu(self): """Menu principal simplifie""" self.main_menu = tk.Tk() self.main_menu.title("LastWar - Gestionnaire de Templates") self.main_menu.geometry("500x600") self.main_menu.resizable(False, False) # Titre title = tk.Label(self.main_menu, text="LastWar - Gestionnaire de Templates", font=("Arial", 16, "bold"), fg="darkblue") title.pack(pady=15) # Section Presets preset_frame = tk.LabelFrame(self.main_menu, text="PRESETS DE TEMPLATES", font=("Arial", 12, "bold"), padx=10, pady=10) preset_frame.pack(fill="both", expand=True, padx=20, pady=10) # Liste des presets list_container = tk.Frame(preset_frame) list_container.pack(fill="both", expand=True, pady=5) scrollbar = tk.Scrollbar(list_container) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.preset_listbox = tk.Listbox(list_container, yscrollcommand=scrollbar.set, font=("Arial", 11), height=10) self.preset_listbox.pack(side=tk.LEFT, fill="both", expand=True) scrollbar.config(command=self.preset_listbox.yview) self.refresh_preset_list() # Info preset actuel self.current_preset_label = tk.Label(preset_frame, text=f"Preset actuel: {self.preset_manager.current_preset or 'Aucun'}", font=("Arial", 10, "italic"), fg="gray") self.current_preset_label.pack(pady=5) # Boutons de gestion des presets btn_frame = tk.Frame(preset_frame) btn_frame.pack(pady=10) tk.Button(btn_frame, text="Creer Nouveau Preset", command=self.create_new_preset, bg="green", fg="white", font=("Arial", 10, "bold"), width=20, height=2).grid(row=0, column=0, padx=5, pady=2) tk.Button(btn_frame, text="Charger & Demarrer", command=self.load_and_start_preset, bg="blue", fg="white", font=("Arial", 10, "bold"), width=20, height=2).grid(row=0, column=1, padx=5, pady=2) tk.Button(btn_frame, text="Editer Preset", command=self.edit_selected_preset, bg="orange", fg="white", font=("Arial", 9), width=20).grid(row=1, column=0, padx=5, pady=2) tk.Button(btn_frame, text="Supprimer Preset", command=self.delete_selected_preset, bg="red", fg="white", font=("Arial", 9), width=20).grid(row=1, column=1, padx=5, pady=2) tk.Button(btn_frame, text="Renommer Preset", command=self.rename_selected_preset, bg="purple", fg="white", font=("Arial", 9), width=20).grid(row=2, column=0, columnspan=2, padx=5, pady=2) # Bouton quitter tk.Button(self.main_menu, text="Quitter", command=self.quit_application, bg="gray", fg="white", font=("Arial", 10), width=20).pack(pady=10) self.main_menu.protocol("WM_DELETE_WINDOW", self.quit_application) self.main_menu.mainloop() def refresh_preset_list(self): """Rafraichit la liste des presets""" self.preset_listbox.delete(0, tk.END) for name in self.preset_manager.get_preset_names(): templates_count = len(self.preset_manager.presets[name]['templates']) self.preset_listbox.insert(tk.END, f"{name} ({templates_count} templates)") def create_new_preset(self): """Cree un nouveau preset""" name = simpledialog.askstring("Nouveau Preset", "Nom du nouveau preset:", parent=self.main_menu) if not name: return if name in self.preset_manager.presets: messagebox.showerror("Erreur", f"Le preset '{name}' existe deja") return # Cacher le menu principal self.main_menu.withdraw() # Lancer la capture self.captured_templates = [] if self.create_capture_interface(): if hasattr(self, 'capture_window') and self.capture_window: self.capture_window.wait_window() # Verifier le resultat if self.captured_templates and len(self.captured_templates) >= 2: # Sauvegarder comme nouveau preset if self.preset_manager.add_preset(name, self.captured_templates): self.refresh_preset_list() # Reafficher le menu self.main_menu.deiconify() def load_and_start_preset(self): """Charge le preset selectionne et demarre la detection""" selection = self.preset_listbox.curselection() if not selection: messagebox.showwarning("Aucune selection", "Selectionnez un preset") return preset_name = self.preset_listbox.get(selection[0]).split(" (")[0] templates = self.preset_manager.load_preset(preset_name) if templates: self.captured_templates = templates for template in self.captured_templates: template['strict_threshold'] = self.current_threshold # Fermer le menu principal self.main_menu.destroy() self.main_menu = None # Demarrer la detection self.mode = "detection" self.start_overlay_detection() def edit_selected_preset(self): """Edite le preset selectionne""" selection = self.preset_listbox.curselection() if not selection: messagebox.showwarning("Aucune selection", "Selectionnez un preset") return preset_name = self.preset_listbox.get(selection[0]).split(" (")[0] templates = self.preset_manager.load_preset(preset_name) if templates: self.captured_templates = templates # Cacher le menu self.main_menu.withdraw() # Ouvrir l'editeur self.open_template_editor(preset_name) def delete_selected_preset(self): """Supprime le preset selectionne""" selection = self.preset_listbox.curselection() if not selection: messagebox.showwarning("Aucune selection", "Selectionnez un preset") return preset_name = self.preset_listbox.get(selection[0]).split(" (")[0] if messagebox.askyesno("Confirmer", f"Supprimer le preset '{preset_name}' ?"): if self.preset_manager.delete_preset(preset_name): self.refresh_preset_list() def rename_selected_preset(self): """Renomme le preset selectionne""" selection = self.preset_listbox.curselection() if not selection: messagebox.showwarning("Aucune selection", "Selectionnez un preset") return old_name = self.preset_listbox.get(selection[0]).split(" (")[0] new_name = simpledialog.askstring("Renommer", f"Nouveau nom pour '{old_name}':", parent=self.main_menu) if new_name and new_name != old_name: if self.preset_manager.rename_preset(old_name, new_name): self.refresh_preset_list() # ========== EDITEUR DE TEMPLATES ========== def open_template_editor(self, preset_name): """Ouvre l'editeur de templates pour un preset""" editor = tk.Toplevel() editor.title(f"Edition: {preset_name}") editor.geometry("400x600") # Titre tk.Label(editor, text=f"Edition: {preset_name}", font=("Arial", 14, "bold")).pack(pady=10) # Liste des templates list_frame = tk.LabelFrame(editor, text="Templates", font=("Arial", 10, "bold")) list_frame.pack(fill="both", expand=True, padx=10, pady=10) template_list = tk.Listbox(list_frame, font=("Arial", 10), height=15) template_list.pack(fill="both", expand=True, padx=5, pady=5) def refresh_template_list(): template_list.delete(0, tk.END) for i, tmpl in enumerate(self.captured_templates): size = tmpl.get('size', (0, 0)) template_list.insert(tk.END, f"Template {i}: {size[0]}x{size[1]}") refresh_template_list() # Boutons d'action btn_frame = tk.Frame(editor) btn_frame.pack(pady=10) def add_template(): editor.withdraw() self.create_template_capture_window() # Attendre la fermeture editor.after(100, lambda: check_capture_done(editor)) def check_capture_done(ed): # Verifier periodiquement si la capture est terminee if hasattr(self, 'capture_window') and self.capture_window and self.capture_window.winfo_exists(): ed.after(100, lambda: check_capture_done(ed)) else: refresh_template_list() ed.deiconify() def delete_template(): sel = template_list.curselection() if sel: idx = sel[0] if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"): self.captured_templates.pop(idx) refresh_template_list() def save_changes(): if self.preset_manager.add_preset(preset_name, self.captured_templates): editor.destroy() self.main_menu.deiconify() self.refresh_preset_list() def cancel_edit(): editor.destroy() self.main_menu.deiconify() tk.Button(btn_frame, text="Ajouter Template", command=add_template, bg="green", fg="white", width=18).pack(pady=2) tk.Button(btn_frame, text="Supprimer Template", command=delete_template, bg="red", fg="white", width=18).pack(pady=2) tk.Button(btn_frame, text="Sauvegarder", command=save_changes, bg="blue", fg="white", width=18, font=("Arial", 10, "bold")).pack(pady=10) tk.Button(btn_frame, text="Annuler", command=cancel_edit, bg="gray", fg="white", width=18).pack(pady=2) # ========== CAPTURE DE TEMPLATES ========== def create_capture_interface(self): """Overlay transparent pour capturer des templates""" try: print("Mode capture - Cliquez sur le jeu pour selectionner") print(" Entree = Valider | Echap = Annuler") self.capture_window = tk.Toplevel() self.capture_window.title("CAPTURE TEMPLATES") self.capture_window.attributes("-topmost", True) self.capture_window.attributes("-alpha", 0.3) self.capture_window.geometry(f"{self.target_window.width}x{self.target_window.height}+{self.target_window.left}+{self.target_window.top}") self.capture_window.overrideredirect(True) self.capture_canvas = tk.Canvas(self.capture_window, width=self.target_window.width, height=self.target_window.height, highlightthickness=0, bg='gray') self.capture_canvas.pack() self.capture_canvas.create_text( self.target_window.width // 2, 30, text="CLIQUEZ SUR LES TEMPLATES (2 minimum)\nEntree = Valider | Echap = Annuler", fill="yellow", font=("Arial", 14, "bold"), justify=tk.CENTER, tags="instructions" ) self.counter_text = self.capture_canvas.create_text( self.target_window.width // 2, 70, text="Templates: 0/2 minimum", fill="white", font=("Arial", 12, "bold"), tags="counter" ) def on_click(event): if len(self.captured_templates) >= 8: return image = self.capture_window_fast(self.target_window) if image is None: return x, y = event.x, event.y gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) size = 24 x1 = max(0, x - size//2) y1 = max(0, y - size//2) x2 = min(gray.shape[1], x + size//2) y2 = min(gray.shape[0], y + size//2) template = gray[y1:y2, x1:x2] if template.size > 100: self.captured_templates.append({ 'template': template, 'size': (x2-x1, y2-y1), 'position': (x, y), 'strict_threshold': self.current_threshold }) self.capture_canvas.create_rectangle( x - size//2, y - size//2, x + size//2, y + size//2, outline="green", width=3, tags="selection" ) self.capture_canvas.create_text( x, y, text=str(len(self.captured_templates)), fill="green", font=("Arial", 16, "bold"), tags="selection" ) count = len(self.captured_templates) color = "lime" if count >= 2 else "white" self.capture_canvas.itemconfig(self.counter_text, text=f"Templates: {count}/2 minimum (max 8)", fill=color) def validate(): if len(self.captured_templates) >= 2: self.capture_window.destroy() else: print("Minimum 2 templates requis") def cancel(): self.captured_templates = [] self.capture_window.destroy() self.capture_canvas.bind("", on_click) self.capture_window.bind("", lambda e: validate()) self.capture_window.bind("", lambda e: cancel()) self.capture_window.focus_set() return True except Exception as e: print(f"Erreur creation interface: {e}") return False def create_template_capture_window(self): """Overlay pour ajouter UN template (mode edition) avec detection visible""" try: temp_overlay = tk.Toplevel() temp_overlay.title("AJOUT TEMPLATE") temp_overlay.attributes("-topmost", True) temp_overlay.attributes("-alpha", 0.3) temp_overlay.geometry(f"{self.target_window.width}x{self.target_window.height}+{self.target_window.left}+{self.target_window.top}") temp_overlay.overrideredirect(True) canvas = tk.Canvas(temp_overlay, width=self.target_window.width, height=self.target_window.height, highlightthickness=0, bg='gray') canvas.pack() canvas.create_text( self.target_window.width // 2, 30, text="CLIQUEZ POUR AJOUTER UN TEMPLATE\nEchap pour annuler", fill="yellow", font=("Arial", 16, "bold"), justify=tk.CENTER ) # Variable pour suivre l'etat is_active = [True] def update_detections(): """Met a jour l'affichage des detections existantes""" if not is_active[0]: return try: # Capturer l'image actuelle image = self.capture_window_fast(self.target_window) if image is not None: # Detecter avec les templates existants positions = self.find_matches_with_templates(image) # Effacer les anciennes detections canvas.delete("detection") # Afficher les detections en rouge for x, y, w, h in positions: margin = 4 x1 = max(0, x - margin) y1 = max(0, y - margin) x2 = x + w + margin y2 = y + h + margin canvas.create_rectangle( x1, y1, x2, y2, outline="#FF0000", width=2, fill="", tags="detection" ) # Continuer a mettre a jour if is_active[0]: temp_overlay.after(100, update_detections) except: pass def on_click(event): image = self.capture_window_fast(self.target_window) if image is None: return x, y = event.x, event.y if self.add_template_at_position(image, x, y): canvas.create_oval(x-30, y-30, x+30, y+30, outline="green", width=4) canvas.create_text(x, y, text="OK", fill="green", font=("Arial", 32, "bold")) is_active[0] = False temp_overlay.after(1000, temp_overlay.destroy) else: canvas.create_text(x, y, text="X", fill="red", font=("Arial", 32, "bold")) def on_close(): is_active[0] = False temp_overlay.destroy() canvas.bind("", on_click) temp_overlay.bind("", lambda e: on_close()) temp_overlay.focus_set() # Demarrer la mise a jour des detections update_detections() except Exception as e: print(f"Erreur overlay capture: {e}") def add_template_at_position(self, image, x, y): """Ajoute un template a la position specifiee""" try: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) size = 24 x1 = max(0, x - size//2) y1 = max(0, y - size//2) x2 = min(gray.shape[1], x + size//2) y2 = min(gray.shape[0], y + size//2) template = gray[y1:y2, x1:x2] if template.size > 100: self.captured_templates.append({ 'template': template, 'size': (x2-x1, y2-y1), 'position': (x, y), 'strict_threshold': self.current_threshold }) print(f"Template ajoute ({x2-x1}x{y2-y1})") # Sauvegarder et rafraîchir l'affichage if self.preset_manager.current_preset: self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) # Rafraîchir la liste dans la fenêtre de contrôle if hasattr(self, 'refresh_templates_display'): try: self.refresh_templates_display() except: pass return True return False except Exception as e: print(f"Erreur ajout template: {e}") return False # ========== UTILITAIRES ========== def find_game_window(self): try: windows = gw.getWindowsWithTitle("Last War-Survival Game") if windows: return windows[0] all_windows = gw.getAllWindows() for window in all_windows: if "last war" in window.title.lower(): return window return None except: return None def capture_window_fast(self, window): try: left, top, width, height = window.left, window.top, window.width, window.height if width <= 0 or height <= 0: return None current_bbox = (left, top, left + width, top + height) screenshot = ImageGrab.grab(current_bbox, all_screens=False) return np.array(screenshot) except: return None def save_settings(self): try: settings = {'threshold': self.current_threshold, 'version': '1.0'} with open(self.settings_file, 'wb') as f: pickle.dump(settings, f) return True except: return False def load_settings(self): try: if os.path.exists(self.settings_file): with open(self.settings_file, 'rb') as f: settings = pickle.load(f) self.current_threshold = settings.get('threshold', 0.85) return True return False except: return False def adjust_threshold_up(self): if self.current_threshold < self.max_threshold: self.current_threshold = min(self.max_threshold, self.current_threshold + self.threshold_step) for template in self.captured_templates: template['strict_threshold'] = self.current_threshold print(f"Seuil: {self.current_threshold:.2f} (plus strict)") self.update_threshold_display() self.save_settings() def adjust_threshold_down(self): if self.current_threshold > self.min_threshold: self.current_threshold = max(self.min_threshold, self.current_threshold - self.threshold_step) for template in self.captured_templates: template['strict_threshold'] = self.current_threshold print(f"Seuil: {self.current_threshold:.2f} (moins strict)") self.update_threshold_display() self.save_settings() def reset_threshold(self): self.current_threshold = 0.85 for template in self.captured_templates: template['strict_threshold'] = self.current_threshold print(f"Seuil: {self.current_threshold:.2f}") self.update_threshold_display() self.save_settings() # ========== DETECTION ========== def find_matches_single_template(self, gray_image, template_data, template_id): try: template = template_data['template'] threshold = template_data.get('strict_threshold', self.current_threshold) if template.size == 0: return [] result = cv2.matchTemplate(gray_image, template, cv2.TM_CCOEFF_NORMED) locations = np.where(result >= threshold) matches = [] for pt in zip(*locations[::-1]): x, y = pt h, w = template.shape score = result[y, x] if score >= threshold: matches.append({ 'x': x, 'y': y, 'w': w, 'h': h, 'score': score, 'template_id': template_id }) return matches except Exception as e: return [] def find_matches_with_templates(self, image): positions = [] if not self.captured_templates: return positions try: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) futures = [] for i, template_data in enumerate(self.captured_templates): future = self.executor.submit( self.find_matches_single_template, gray, template_data, i ) futures.append(future) all_matches = [] for future in futures: try: matches = future.result(timeout=0.1) all_matches.extend(matches) except: continue all_matches.sort(key=lambda m: m['score'], reverse=True) template_counts = {} debug_matches = [] for match in all_matches: x, y, w, h = match['x'], match['y'], match['w'], match['h'] score = match['score'] template_id = match['template_id'] if template_counts.get(template_id, 0) >= 15: continue is_duplicate = False for existing_x, existing_y, _, _ in positions: if abs(x - existing_x) < 15 and abs(y - existing_y) < 10: is_duplicate = True break if not is_duplicate and score >= self.current_threshold: positions.append((x, y, w, h)) debug_matches.append((x, y, w, h, template_id, score)) template_counts[template_id] = template_counts.get(template_id, 0) + 1 if len(positions) >= 25: break self.debug_matches = debug_matches except Exception as e: print(f"Erreur matching: {e}") return positions def toggle_debug_mode(self): if self.template_mode == "debug": self.template_mode = "normal" print("Mode debug desactive") else: self.template_mode = "debug" print("Mode debug active") self.update_control_interface() def update_overlay(self, positions): if not self.canvas or not self.overlay_root or not self.running: return try: if not self.overlay_root.winfo_exists(): return if positions != self.last_positions: self.canvas.delete("detection") if self.template_mode == "debug" and hasattr(self, 'debug_matches'): for x, y, w, h, template_id, score in self.debug_matches: margin = 4 x1 = max(0, x - margin) y1 = max(0, y - margin) x2 = x + w + margin y2 = y + h + margin colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#FFA500", "#800080"] color = colors[template_id % len(colors)] self.canvas.create_rectangle( x1, y1, x2, y2, outline=color, width=2, tags="detection" ) self.canvas.create_text( x + w//2, y + h//2, text=f"T{template_id}", fill=color, font=("Arial", 10, "bold"), tags="detection" ) self.canvas.create_text( x + w//2, y + h + 15, text=f"{score:.2f}", fill=color, font=("Arial", 8), tags="detection" ) else: for x, y, w, h in positions: margin = 4 x1 = max(0, x - margin) y1 = max(0, y - margin) x2 = x + w + margin y2 = y + h + margin self.canvas.create_rectangle( x1, y1, x2, y2, outline="#FF0000", width=2, fill="", tags="detection" ) self.last_positions = positions.copy() except (tk.TclError, RuntimeError): self.running = False def safe_update_overlay(self, positions): if self.overlay_root and self.running: try: self.overlay_root.after(0, lambda: self.update_overlay(positions)) except (tk.TclError, RuntimeError): self.running = False def create_overlay(self, window): try: self.overlay_root = tk.Toplevel(self.control_window) self.overlay_root.title("LastWar Detection Active") self.overlay_root.attributes("-alpha", 0.85) self.overlay_root.attributes("-topmost", True) self.overlay_root.overrideredirect(True) self.overlay_root.geometry(f"{window.width}x{window.height}+{window.left}+{window.top}") self.canvas = tk.Canvas( self.overlay_root, width=window.width, height=window.height, highlightthickness=0, bg='black' ) self.canvas.pack() self.overlay_root.attributes("-transparentcolor", "black") self.overlay_root.bind("", lambda e: self.stop()) self.overlay_root.protocol("WM_DELETE_WINDOW", self.stop) self.overlay_created = True print("Overlay detection actif") except Exception as e: print(f"Erreur overlay: {e}") self.running = False def create_control_window(self): try: self.control_window = tk.Toplevel() self.control_window.title("Controles Detection") self.control_window.geometry("700x700") self.control_window.attributes("-topmost", True) # Section seuil seuil_frame = tk.LabelFrame(self.control_window, text="AJUSTEMENT SEUIL", font=("Arial", 10, "bold")) seuil_frame.pack(fill="x", padx=5, pady=5) self.threshold_label = tk.Label(seuil_frame, text=f"Seuil: {self.current_threshold:.2f}", font=("Arial", 12, "bold"), fg="green") self.threshold_label.pack(pady=5) btn_frame = tk.Frame(seuil_frame) btn_frame.pack(pady=5) tk.Button(btn_frame, text="+ STRICT", command=self.adjust_threshold_up, bg="red", fg="white", font=("Arial", 9, "bold"), width=12).pack(side=tk.LEFT, padx=2) tk.Button(btn_frame, text="- STRICT", command=self.adjust_threshold_down, bg="orange", fg="white", font=("Arial", 9, "bold"), width=12).pack(side=tk.LEFT, padx=2) tk.Button(seuil_frame, text="RESET SEUIL", command=self.reset_threshold, bg="blue", fg="white", font=("Arial", 8), width=20).pack(pady=2) tk.Label(seuil_frame, text="(Sauvegarde automatique)", font=("Arial", 8, "italic"), fg="gray").pack() # Section templates avec liste directe template_frame = tk.LabelFrame(self.control_window, text="GESTION TEMPLATES", font=("Arial", 10, "bold")) template_frame.pack(fill="both", expand=True, padx=5, pady=5) # Bouton ajouter en haut tk.Button(template_frame, text="+ AJOUTER TEMPLATE", command=self.add_template_during_detection, bg="green", fg="white", font=("Arial", 10, "bold"), width=30).pack(pady=5) # Zone scrollable pour la liste des templates list_frame = tk.Frame(template_frame) list_frame.pack(fill="both", expand=True, padx=5, pady=5) canvas_scroll = tk.Canvas(list_frame) scrollbar = tk.Scrollbar(list_frame, orient="vertical", command=canvas_scroll.yview) self.templates_list_frame = tk.Frame(canvas_scroll) self.templates_list_frame.bind( "", lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all")) ) canvas_scroll.create_window((0, 0), window=self.templates_list_frame, anchor="nw") canvas_scroll.configure(yscrollcommand=scrollbar.set) canvas_scroll.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # Stocker les images pour tkinter self.template_photos = [] # Afficher les templates self.refresh_templates_display() # Section mode debug mode_frame = tk.LabelFrame(self.control_window, text="MODE DEBUG", font=("Arial", 10, "bold")) mode_frame.pack(fill="x", padx=5, pady=5) self.mode_label = tk.Label(mode_frame, text="Mode Normal", font=("Arial", 10, "bold"), fg="blue") self.mode_label.pack(pady=5) tk.Button(mode_frame, text="DEBUG ON/OFF", command=self.toggle_debug_mode, bg="orange", fg="white", font=("Arial", 9, "bold"), width=20).pack(pady=5) # Bouton retour menu tk.Button(self.control_window, text="<< RETOUR MENU PRINCIPAL", command=self.return_to_main_menu, bg="purple", fg="white", font=("Arial", 10, "bold"), width=30).pack(pady=10) self.control_window.bind("", self.handle_keypress) self.control_window.focus_set() self.control_window.protocol("WM_DELETE_WINDOW", self.return_to_main_menu) except Exception as e: print(f"Erreur fenetre controle: {e}") def handle_keypress(self, event): key = event.keysym.lower() if key in ['up', 'plus', 'equal']: self.adjust_threshold_up() elif key in ['down', 'minus']: self.adjust_threshold_down() elif key == 'r': self.reset_threshold() elif key == 'a': self.add_template_during_detection() elif key == 'd': self.toggle_debug_mode() elif key == 'escape': self.return_to_main_menu() def refresh_templates_display(self): """Rafraichit l'affichage de la liste des templates""" if not hasattr(self, 'templates_list_frame'): return # Effacer for widget in self.templates_list_frame.winfo_children(): widget.destroy() self.template_photos.clear() if not self.captured_templates: tk.Label(self.templates_list_frame, text="Aucun template\nUtilisez 'AJOUTER TEMPLATE'", font=("Arial", 10), fg="gray").pack(pady=20) return for i, tmpl in enumerate(self.captured_templates): # Frame pour chaque template template_frame = tk.Frame(self.templates_list_frame, relief=tk.RIDGE, borderwidth=2, bg="white") template_frame.pack(fill="x", padx=2, pady=3) # Image du template try: template_data = tmpl['template'] scale = 3 h, w = template_data.shape template_resized = cv2.resize(template_data, (w*scale, h*scale), interpolation=cv2.INTER_NEAREST) if len(template_resized.shape) == 2: template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB) else: template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_BGR2RGB) pil_image = Image.fromarray(template_rgb) photo = ImageTk.PhotoImage(pil_image) self.template_photos.append(photo) img_label = tk.Label(template_frame, image=photo, bg="white") img_label.pack(side=tk.LEFT, padx=5, pady=5) except: tk.Label(template_frame, text="[Img]", bg="white").pack(side=tk.LEFT, padx=5) # Infos info_frame = tk.Frame(template_frame, bg="white") info_frame.pack(side=tk.LEFT, fill="both", expand=True, padx=5) tk.Label(info_frame, text=f"T{i}", font=("Arial", 10, "bold"), bg="white").pack(anchor="w") size = tmpl.get('size', (0, 0)) tk.Label(info_frame, text=f"{size[0]}x{size[1]}px", font=("Arial", 8), bg="white").pack(anchor="w") # Boutons btn_frame = tk.Frame(template_frame, bg="white") btn_frame.pack(side=tk.RIGHT, padx=5) tk.Button(btn_frame, text="Recadrer", command=lambda idx=i: self.recrop_from_control(idx), bg="orange", fg="white", font=("Arial", 8), width=9).pack(pady=1) tk.Button(btn_frame, text="Supprimer", command=lambda idx=i: self.delete_template_from_control(idx), bg="red", fg="white", font=("Arial", 8), width=9).pack(pady=1) def recrop_from_control(self, idx): """Recadre depuis la fenetre de controle""" self.recrop_template_inline(idx, self.refresh_templates_display) def delete_template_from_control(self, idx): """Supprime depuis la fenetre de controle""" if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"): self.captured_templates.pop(idx) self.refresh_templates_display() if self.preset_manager.current_preset: self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) def add_template_during_detection(self): """Ajoute un template pendant la detection""" print("Mode ajout template active...") self.create_template_capture_window() def open_delete_menu(self): """Gestionnaire visuel complet des templates""" if not self.captured_templates: messagebox.showinfo("Aucun template", "Aucun template disponible") return manager_window = tk.Toplevel(self.control_window) manager_window.title("Gestion des Templates") manager_window.geometry("700x600") manager_window.attributes("-topmost", True) tk.Label(manager_window, text="Gestion des Templates", font=("Arial", 12, "bold")).pack(pady=10) # Frame principal avec canvas scrollable main_frame = tk.Frame(manager_window) main_frame.pack(fill="both", expand=True, padx=10, pady=5) canvas_scroll = tk.Canvas(main_frame) scrollbar = tk.Scrollbar(main_frame, orient="vertical", command=canvas_scroll.yview) scrollable_frame = tk.Frame(canvas_scroll) scrollable_frame.bind( "", lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all")) ) canvas_scroll.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas_scroll.configure(yscrollcommand=scrollbar.set) canvas_scroll.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # Images de reference pour tkinter template_images = [] def refresh_templates(): # Effacer for widget in scrollable_frame.winfo_children(): widget.destroy() template_images.clear() for i, tmpl in enumerate(self.captured_templates): # Frame pour chaque template template_frame = tk.Frame(scrollable_frame, relief=tk.RIDGE, borderwidth=2, bg="white") template_frame.pack(fill="x", padx=5, pady=5) # Image du template (agrandie) try: template_data = tmpl['template'] # Agrandir x4 pour voir les details scale = 4 h, w = template_data.shape new_size = (w * scale, h * scale) template_resized = cv2.resize(template_data, new_size, interpolation=cv2.INTER_NEAREST) # Convertir pour tkinter if len(template_resized.shape) == 2: template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB) else: template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_BGR2RGB) pil_image = Image.fromarray(template_rgb) photo = ImageTk.PhotoImage(pil_image) template_images.append(photo) img_label = tk.Label(template_frame, image=photo, bg="white") img_label.pack(side=tk.LEFT, padx=10, pady=10) except Exception as e: tk.Label(template_frame, text="Erreur image", bg="white").pack(side=tk.LEFT, padx=10) # Infos info_frame = tk.Frame(template_frame, bg="white") info_frame.pack(side=tk.LEFT, fill="both", expand=True, padx=10) tk.Label(info_frame, text=f"Template {i}", font=("Arial", 12, "bold"), bg="white").pack(anchor="w") size = tmpl.get('size', (0, 0)) tk.Label(info_frame, text=f"Taille: {size[0]}x{size[1]} pixels", font=("Arial", 9), bg="white").pack(anchor="w") threshold = tmpl.get('strict_threshold', self.current_threshold) tk.Label(info_frame, text=f"Seuil: {threshold:.2f}", font=("Arial", 9), bg="white").pack(anchor="w") # Boutons btn_frame = tk.Frame(template_frame, bg="white") btn_frame.pack(side=tk.RIGHT, padx=10) tk.Button(btn_frame, text="Recadrer", command=lambda idx=i: recrop_and_refresh(idx), bg="orange", fg="white", font=("Arial", 9), width=10).pack(pady=2) tk.Button(btn_frame, text="Supprimer", command=lambda idx=i: delete_and_refresh(idx), bg="red", fg="white", font=("Arial", 9), width=10).pack(pady=2) def recrop_and_refresh(idx): """Recadre et rafraichit l'affichage""" self.recrop_template_inline(idx, refresh_templates) def delete_and_refresh(idx): if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"): self.captured_templates.pop(idx) refresh_templates() self.update_control_interface() if self.preset_manager.current_preset: self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) refresh_templates() tk.Button(manager_window, text="Fermer", command=manager_window.destroy, font=("Arial", 10), width=20).pack(pady=10) def recrop_template_inline(self, template_idx, callback_refresh): """Recadre le template et rafraichit automatiquement""" if template_idx >= len(self.captured_templates): return try: current_template = self.captured_templates[template_idx] template_image = current_template['template'] # Fenetre de recadrage crop_window = tk.Toplevel() crop_window.title(f"Recadrage Template {template_idx}") crop_window.geometry("700x600") crop_window.attributes("-topmost", True) crop_window.grab_set() # Modale tk.Label(crop_window, text=f"Recadrage Template {template_idx}", font=("Arial", 14, "bold")).pack(pady=10) tk.Label(crop_window, text="Cliquez et glissez pour tracer le rectangle de selection", font=("Arial", 10), fg="blue").pack() # Agrandir l'image scale = 8 h, w = template_image.shape template_resized = cv2.resize(template_image, (w*scale, h*scale), interpolation=cv2.INTER_NEAREST) template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB) # Canvas canvas_frame = tk.Frame(crop_window, relief=tk.SUNKEN, borderwidth=2) canvas_frame.pack(pady=10, padx=20) canvas = tk.Canvas(canvas_frame, width=w*scale, height=h*scale, bg="white", cursor="cross") canvas.pack(padx=5, pady=5) # Afficher l'image pil_img = Image.fromarray(template_rgb) photo = ImageTk.PhotoImage(pil_img) canvas.create_image(0, 0, anchor=tk.NW, image=photo, tags="image") canvas.image = photo # Variables pour le rectangle start_x = None start_y = None rect_id = None selection = [None, None, None, None] def on_mouse_down(event): nonlocal start_x, start_y, rect_id start_x = event.x start_y = event.y if rect_id: canvas.delete(rect_id) def on_mouse_drag(event): nonlocal rect_id if start_x is not None and start_y is not None: if rect_id: canvas.delete(rect_id) x = max(0, min(event.x, w*scale)) y = max(0, min(event.y, h*scale)) rect_id = canvas.create_rectangle( start_x, start_y, x, y, outline="red", width=2, tags="selection" ) width_px = abs(x - start_x) // scale height_px = abs(y - start_y) // scale size_label.config(text=f"Selection: {width_px}x{height_px} pixels") def on_mouse_up(event): nonlocal start_x, start_y if start_x is not None and start_y is not None: x = max(0, min(event.x, w*scale)) y = max(0, min(event.y, h*scale)) x1 = min(start_x, x) // scale y1 = min(start_y, y) // scale x2 = max(start_x, x) // scale y2 = max(start_y, y) // scale selection[0] = x1 selection[1] = y1 selection[2] = x2 selection[3] = y2 width_px = x2 - x1 height_px = y2 - y1 size_label.config(text=f"Selection: {width_px}x{height_px} pixels") canvas.bind("", on_mouse_down) canvas.bind("", on_mouse_drag) canvas.bind("", on_mouse_up) # Info info_frame = tk.Frame(crop_window) info_frame.pack(pady=5) size_label = tk.Label(info_frame, text="Tracez un rectangle sur l'image", font=("Arial", 10)) size_label.pack() # Boutons btn_frame = tk.Frame(crop_window) btn_frame.pack(pady=15) def apply_crop(): if selection[0] is None: messagebox.showwarning("Aucune selection", "Tracez d'abord un rectangle") return x1, y1, x2, y2 = selection if x2 - x1 < 8 or y2 - y1 < 8: messagebox.showerror("Selection trop petite", "Minimum 8x8 pixels") return # Extraire le nouveau template cropped = template_image[y1:y2, x1:x2] # Mettre a jour self.captured_templates[template_idx] = { 'template': cropped, 'size': (x2-x1, y2-y1), 'position': current_template.get('position', (0, 0)), 'strict_threshold': self.current_threshold } # Sauvegarder if self.preset_manager.current_preset: self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) # Fermer et rafraichir crop_window.destroy() callback_refresh() def reset_selection(): nonlocal rect_id, start_x, start_y if rect_id: canvas.delete(rect_id) rect_id = None start_x = None start_y = None selection[0] = None selection[1] = None selection[2] = None selection[3] = None size_label.config(text="Tracez un rectangle sur l'image") tk.Button(btn_frame, text="Appliquer", command=apply_crop, bg="green", fg="white", font=("Arial", 10, "bold"), width=12).grid(row=0, column=0, padx=5) tk.Button(btn_frame, text="Reinitialiser", command=reset_selection, bg="orange", fg="white", font=("Arial", 10), width=12).grid(row=0, column=1, padx=5) tk.Button(btn_frame, text="Annuler", command=crop_window.destroy, bg="gray", fg="white", font=("Arial", 10), width=12).grid(row=0, column=2, padx=5) except Exception as e: print(f"Erreur recadrage: {e}") """Ouvre un menu pour visualiser, supprimer ou editer des templates""" if not self.captured_templates: messagebox.showinfo("Aucun template", "Aucun template a supprimer") return manager_window = tk.Toplevel(self.control_window) manager_window.title("Gestionnaire de Templates") manager_window.geometry("600x500") manager_window.attributes("-topmost", True) tk.Label(manager_window, text="Gestion des Templates", font=("Arial", 12, "bold")).pack(pady=10) # Frame principal avec canvas scrollable main_frame = tk.Frame(manager_window) main_frame.pack(fill="both", expand=True, padx=10, pady=5) canvas_scroll = tk.Canvas(main_frame) scrollbar = tk.Scrollbar(main_frame, orient="vertical", command=canvas_scroll.yview) scrollable_frame = tk.Frame(canvas_scroll) scrollable_frame.bind( "", lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all")) ) canvas_scroll.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas_scroll.configure(yscrollcommand=scrollbar.set) canvas_scroll.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # Images de reference pour tkinter template_images = [] def refresh_templates(): # Effacer for widget in scrollable_frame.winfo_children(): widget.destroy() template_images.clear() for i, tmpl in enumerate(self.captured_templates): # Frame pour chaque template template_frame = tk.Frame(scrollable_frame, relief=tk.RIDGE, borderwidth=2, bg="white") template_frame.pack(fill="x", padx=5, pady=5) # Image du template (agrandie) try: template_data = tmpl['template'] # Agrandir x4 pour voir les details scale = 4 h, w = template_data.shape new_size = (w * scale, h * scale) template_resized = cv2.resize(template_data, new_size, interpolation=cv2.INTER_NEAREST) # Convertir pour tkinter if len(template_resized.shape) == 2: template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB) else: template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_BGR2RGB) pil_image = Image.fromarray(template_rgb) photo = ImageTk.PhotoImage(pil_image) template_images.append(photo) img_label = tk.Label(template_frame, image=photo, bg="white") img_label.pack(side=tk.LEFT, padx=10, pady=10) except Exception as e: tk.Label(template_frame, text="Erreur image", bg="white").pack(side=tk.LEFT, padx=10) # Infos info_frame = tk.Frame(template_frame, bg="white") info_frame.pack(side=tk.LEFT, fill="both", expand=True, padx=10) tk.Label(info_frame, text=f"Template {i}", font=("Arial", 12, "bold"), bg="white").pack(anchor="w") size = tmpl.get('size', (0, 0)) tk.Label(info_frame, text=f"Taille: {size[0]}x{size[1]} pixels", font=("Arial", 9), bg="white").pack(anchor="w") threshold = tmpl.get('strict_threshold', self.current_threshold) tk.Label(info_frame, text=f"Seuil: {threshold:.2f}", font=("Arial", 9), bg="white").pack(anchor="w") # Boutons btn_frame = tk.Frame(template_frame, bg="white") btn_frame.pack(side=tk.RIGHT, padx=10) tk.Button(btn_frame, text="Recadrer", command=lambda idx=i: recrop_and_refresh(idx), bg="orange", fg="white", font=("Arial", 9), width=10).pack(pady=2) tk.Button(btn_frame, text="Supprimer", command=lambda idx=i: delete_and_refresh(idx), bg="red", fg="white", font=("Arial", 9), width=10).pack(pady=2) def recrop_and_refresh(idx): """Recadre et rafraichit l'affichage""" self.recrop_template_inline(idx, refresh_templates) def delete_and_refresh(idx): if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"): self.captured_templates.pop(idx) refresh_templates() self.update_control_interface() if self.preset_manager.current_preset: self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) refresh_templates() tk.Button(manager_window, text="Fermer", command=manager_window.destroy, font=("Arial", 10), width=20).pack(pady=10) def recrop_template(self, template_idx, parent_window): """Permet de recadrer le template en tracant un rectangle""" if template_idx >= len(self.captured_templates): return parent_window.withdraw() try: current_template = self.captured_templates[template_idx] template_image = current_template['template'] # Fenetre de recadrage crop_window = tk.Toplevel() crop_window.title(f"Recadrage Template {template_idx}") crop_window.geometry("700x600") crop_window.attributes("-topmost", True) tk.Label(crop_window, text=f"Recadrage Template {template_idx}", font=("Arial", 14, "bold")).pack(pady=10) tk.Label(crop_window, text="Cliquez et glissez pour tracer le rectangle de selection", font=("Arial", 10), fg="blue").pack() # Agrandir l'image pour faciliter le recadrage scale = 8 h, w = template_image.shape template_resized = cv2.resize(template_image, (w*scale, h*scale), interpolation=cv2.INTER_NEAREST) template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB) # Frame avec canvas canvas_frame = tk.Frame(crop_window, relief=tk.SUNKEN, borderwidth=2) canvas_frame.pack(pady=10, padx=20) canvas = tk.Canvas(canvas_frame, width=w*scale, height=h*scale, bg="white", cursor="cross") canvas.pack(padx=5, pady=5) # Afficher l'image pil_img = Image.fromarray(template_rgb) photo = ImageTk.PhotoImage(pil_img) canvas.create_image(0, 0, anchor=tk.NW, image=photo, tags="image") canvas.image = photo # Variables pour le rectangle de selection start_x = None start_y = None rect_id = None selection = [None, None, None, None] # x1, y1, x2, y2 def on_mouse_down(event): nonlocal start_x, start_y, rect_id start_x = event.x start_y = event.y if rect_id: canvas.delete(rect_id) def on_mouse_drag(event): nonlocal rect_id if start_x is not None and start_y is not None: if rect_id: canvas.delete(rect_id) # Limiter aux bounds de l'image x = max(0, min(event.x, w*scale)) y = max(0, min(event.y, h*scale)) rect_id = canvas.create_rectangle( start_x, start_y, x, y, outline="red", width=2, tags="selection" ) # Afficher les dimensions width_px = abs(x - start_x) // scale height_px = abs(y - start_y) // scale size_label.config(text=f"Selection: {width_px}x{height_px} pixels") def on_mouse_up(event): nonlocal start_x, start_y if start_x is not None and start_y is not None: # Limiter aux bounds x = max(0, min(event.x, w*scale)) y = max(0, min(event.y, h*scale)) # Enregistrer la selection (en pixels de l'image originale) x1 = min(start_x, x) // scale y1 = min(start_y, y) // scale x2 = max(start_x, x) // scale y2 = max(start_y, y) // scale selection[0] = x1 selection[1] = y1 selection[2] = x2 selection[3] = y2 width_px = x2 - x1 height_px = y2 - y1 size_label.config(text=f"Selection: {width_px}x{height_px} pixels") canvas.bind("", on_mouse_down) canvas.bind("", on_mouse_drag) canvas.bind("", on_mouse_up) # Info info_frame = tk.Frame(crop_window) info_frame.pack(pady=5) size_label = tk.Label(info_frame, text="Tracez un rectangle sur l'image", font=("Arial", 10)) size_label.pack() # Boutons btn_frame = tk.Frame(crop_window) btn_frame.pack(pady=15) def apply_crop(): if selection[0] is None: messagebox.showwarning("Aucune selection", "Tracez d'abord un rectangle") return x1, y1, x2, y2 = selection # Verifier que la selection est valide if x2 - x1 < 8 or y2 - y1 < 8: messagebox.showerror("Selection trop petite", "La selection doit faire au moins 8x8 pixels") return # Extraire le nouveau template cropped = template_image[y1:y2, x1:x2] # Mettre a jour le template self.captured_templates[template_idx] = { 'template': cropped, 'size': (x2-x1, y2-y1), 'position': current_template.get('position', (0, 0)), 'strict_threshold': self.current_threshold } # Sauvegarder if self.preset_manager.current_preset: self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) messagebox.showinfo("Succes", f"Template recadre: {x2-x1}x{y2-y1} pixels") crop_window.destroy() parent_window.deiconify() def reset_selection(): nonlocal rect_id, start_x, start_y if rect_id: canvas.delete(rect_id) rect_id = None start_x = None start_y = None selection[0] = None selection[1] = None selection[2] = None selection[3] = None size_label.config(text="Tracez un rectangle sur l'image") def cancel(): crop_window.destroy() parent_window.deiconify() tk.Button(btn_frame, text="Appliquer", command=apply_crop, bg="green", fg="white", font=("Arial", 10, "bold"), width=12).grid(row=0, column=0, padx=5) tk.Button(btn_frame, text="Reinitialiser", command=reset_selection, bg="orange", fg="white", font=("Arial", 10), width=12).grid(row=0, column=1, padx=5) tk.Button(btn_frame, text="Annuler", command=cancel, bg="gray", fg="white", font=("Arial", 10), width=12).grid(row=0, column=2, padx=5) except Exception as e: print(f"Erreur recadrage: {e}") parent_window.deiconify() def return_to_main_menu(self): """Retourne au menu principal""" # Demander confirmation if messagebox.askyesno("Retour menu", "Arreter la detection et retourner au menu principal ?"): # Arreter la detection self.running = False # Fermer l'overlay et le controle if self.overlay_root: try: self.overlay_root.destroy() except: pass if self.control_window: try: self.control_window.destroy() except: pass # Recreer le menu principal self.mode = "setup" self.create_main_menu() def update_threshold_display(self): if hasattr(self, 'control_window') and self.control_window and hasattr(self, 'threshold_label'): try: if self.control_window.winfo_exists(): self.threshold_label.config(text=f"Seuil: {self.current_threshold:.2f}") self.control_window.update_idletasks() except: pass self.update_control_interface() def update_control_interface(self): if not hasattr(self, 'control_window') or not self.control_window: return try: if hasattr(self, 'mode_label'): mode_text = { "normal": "Mode Normal", "debug": "Mode Debug (IDs visibles)" } mode_color = { "normal": "blue", "debug": "orange" } self.mode_label.config( text=mode_text.get(self.template_mode, "Mode inconnu"), fg=mode_color.get(self.template_mode, "black") ) # Rafraichir la liste des templates self.refresh_templates_display() self.control_window.update_idletasks() except: pass def detection_loop(self): frame_count = 0 last_detection_time = time.time() print("Boucle detection demarree") while self.running and self.mode == "detection": try: if not self.target_window or not self.running: break if self.overlay_root and not self.overlay_root.winfo_exists(): self.running = False break frame_count += 1 current_time = time.time() if self.frame_skip > 0: self.frame_skip -= 1 time.sleep(0.016) continue image = self.capture_window_fast(self.target_window) if image is None: time.sleep(0.02) continue start_detection = time.time() positions = self.find_matches_with_templates(image) detection_time = time.time() - start_detection if detection_time > 0.05: self.frame_skip = self.max_frame_skip elif detection_time > 0.03: self.frame_skip = 1 if self.overlay_created and self.overlay_root and self.running: try: self.safe_update_overlay(positions) except: self.running = False break if current_time - last_detection_time > 2.0: fps = frame_count / (current_time - last_detection_time) if frame_count > 0 else 0 if self.running: print(f"{len(positions)} bases | FPS: {fps:.1f} | {detection_time*1000:.1f}ms | Seuil: {self.current_threshold:.2f}") last_detection_time = current_time frame_count = 0 time.sleep(0.01) except: self.running = False break print("Boucle detection terminee") def start_overlay_detection(self): if not self.target_window: return try: self.create_control_window() self.create_overlay(self.target_window) detection_thread = threading.Thread(target=self.detection_loop, daemon=True) detection_thread.start() print("Detection active") if self.control_window: try: self.control_window.mainloop() except: self.stop() finally: self.running = False except Exception as e: print(f"Erreur demarrage: {e}") self.stop() def start(self): print("LastWar - Gestionnaire de Templates") self.load_settings() self.target_window = self.find_game_window() if not self.target_window: print("Fenetre de jeu non trouvee") return self.running = True self.create_main_menu() def stop(self): self.running = False if hasattr(self, 'control_window') and self.control_window: try: self.control_window.quit() self.control_window.destroy() except: pass if hasattr(self, 'overlay_root') and self.overlay_root: try: self.overlay_root.quit() self.overlay_root.destroy() except: pass if hasattr(self, 'executor'): try: self.executor.shutdown(wait=False) except: pass print("Arrete proprement") import sys sys.exit(0) def quit_application(self): self.running = False if self.main_menu: try: self.main_menu.quit() self.main_menu.destroy() except: pass import sys sys.exit(0) def main(): overlay = GameOverlay() import signal def signal_handler(sig, frame): print("\nArret demande (Ctrl+C)...") overlay.stop() signal.signal(signal.SIGINT, signal_handler) try: overlay.start() except KeyboardInterrupt: print("\nArret demande...") overlay.stop() except Exception as e: print(f"Erreur: {e}") overlay.stop() if __name__ == "__main__": main()