2026/05/31

Braille-nyilvántartó

Volt egy olyan a bakancslistámon, hogy ebben az életben még egyszer szeretnék programozást művelni (kb 26 év után újra). Remélem ezek nem olyan bezáró-zárójelek, amelyekkel egyes életszakaszaimat zárom le, de ez most mindegy is. 

Kapóra jött, hogy volt a cégnél egy régóta megoldandó feladat, plusz a MI fejlődése is lehetővé teszi már, hogy bárki programozóként tetszeleghessen. Nem mellesleg, emiatt mennek tönkre programozói állások. Ahogyan a fotográfia több rendbéli szekularizálódása is mindannyiszor keményen bevágott a fotós szakmának. 

A braille írások domborító kliséinek előkészítéséről, és nyilvántartásáról van szó. Évekig mondogattam, hogy a csomagolóipar egyetlen (EU-s) releváns húzása a braille rányomtatása a dobozra, mert a csillivilli aranyfóliák, meg amúgy a külcsíny, ellene megy minden józan felfogásnak a reklámról, meg a kapitalizmusról általában, de a braille írás legalább alkalmas arra, hogy a vakok ne vegyenek be hashajtóra nyugtatót. 

Aztán szembesültem azzal az adattal, hogy a vakok alig 10 százaléka érti a braille írást. Nabazmeg.

Szóval, a feladat az volt, hogy a Braille-készítő célgép által fogadott és felismert  dxf-eket előállítsuk. Eddig ezeket QCadben csináltuk, a nyilvántartást pedig nyögvenyelősen Illustratorban, Corelben, szemmel ellenőrizve és egérrel összevillogtatva. Na, ezt akarjuk megkönnyíteni, hogy az egyes klisékkel eltöltött átlagos 10 percet, 1-2 perc környékére redukáljuk.

A 6 pontos braille írás szabványa:
- a pontok 1,6mm átmérőjűek, 2,5 milliméterre vannak egymástól, a braille karakterek távolsága 6mm, a soroké pedig 10mm. 20*4 braille karakterrel dolgozunk, ez 480 pontot jelent.

A részfeladatokat külön programokra bontottuk, a könnyebb tesztelhetőség érdekében, illetve néhány modulra, csak egyszer, illetve csak a tesztidőszakban van szükség.

A felület Tkinter, a dxf fileokhoz szükség van a ezdxf python-csomagra. 

Bulldózer

import os
import tkinter as tk
from tkinter import filedialog, scrolledtext
import ezdxf
from ezdxf import recover
import math


# --- 1. MATEMATIKAI MOTOR ÉS KÓDOLÓ ---
class BrailleEngine:
    def __init__(self):
        # Alapértékek (mm)
        self.dot_dist = 2.5
        self.char_dist = 6.0
        self.row_dist = 10.0
        self.tolerance = 0.5
        # Base64 ábécé a 6-bites Braille cellákhoz
        self.b64_chars = (
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
        )

    def bits_to_base64(self, bit_string):
        """480 bitből 80 darab Base64 karaktert csinál (6 bitenként)."""
        b64_result = ""
        for i in range(0, 480, 6):
            six_bits = bit_string[i : i + 6]
            val = 0
            for bit_pos, bit in enumerate(six_bits):
                if bit == "1":
                    val += 1 << bit_pos
            b64_result += self.b64_chars[val]
        return b64_result

    def get_theoretical_grid(self):
        """Legenerálja a 480 rácspont elméleti koordinátáját."""
        grid = []
        for row in range(4):
            for char in range(20):
                for dot_col in range(2):
                    for dot_row in range(3):
                        x = (char * self.char_dist) + (dot_col * self.dot_dist)
                        y = -((row * self.row_dist) + (dot_row * self.dot_dist))
                        grid.append((x, y))
        return grid

    def fit_points(self, points):
        """Rácsra illesztés és Base64 kódolás."""
        if not points:
            return "A" * 80

        min_x = min(p[0] for p in points)
        max_y = max(p[1] for p in points)
        norm_points = [(p[0] - min_x, p[1] - max_y) for p in points]
        theoretical_grid = self.get_theoretical_grid()

        # ez az offset-lista nem mukodott fuggoleges eltolasban, offsets = [(0, 0), (2.5, 0), (0, -2.5), (2.5, -2.5), (0, -5.0), (2.5, -5.0)]
        # ez az offset-lista nem azonos a BraiLuca offsetlistaajaval, pont mast tologatunk itt

        offsets = [
            (0, 0),      # Alap
            (2.5, 0),    # Ha hiányzik a bal oszlop, a pontokat jobbra toljuk a rács 2. oszlopára
            (0, -2.5),   # Ha hiányzik a felső sor, a pontokat lejjebb toljuk a rács 2. sorára
            (2.5, -2.5), # Mindkettő hiányzik
            (0, -5.0),   # Felső két sor hiányzik
            (2.5, -5.0)  # Bal oszlop és felső két sor hiányzik
]


        for ox, oy in offsets:
            fingerprint_bits = ["0"] * 480
            matched_count = 0
            for px, py in norm_points:
                tx, ty = px + ox, py + oy
                for i, (gx, gy) in enumerate(theoretical_grid):
                    dist = math.sqrt((tx - gx) ** 2 + (ty - gy) ** 2)
                    if dist <= self.tolerance:
                        fingerprint_bits[i] = "1"
                        matched_count += 1
                        break

            if matched_count == len(points):
                return self.bits_to_base64("".join(fingerprint_bits))
        return None


# --- 2. GUI ÉS FÁJLKEZELŐ ---
class BrailleBulldozer:
    def __init__(self, root):
        self.root = root
        self.root.title("Braille Buldózer v1.4 - Base64 Expert")
        self.root.geometry("800x600")
        self.engine = BrailleEngine()
        self.selected_path = ""

        # UI Felépítése
        self.btn_browse = tk.Button(
            root,
            text="Forrásmappa kiválasztása",
            command=self.browse_folder,
            bg="#e1e1e1",
            height=2,
        )
        self.btn_browse.pack(pady=10, fill=tk.X, padx=20)

        self.lbl_path = tk.Label(root, text="Nincs mappa kiválasztva", fg="gray")
        self.lbl_path.pack()

        self.log_area = scrolledtext.ScrolledText(
            root, width=90, height=25, font=("Consolas", 9)
        )
        self.log_area.pack(pady=10, padx=20)

        self.btn_start = tk.Button(
            root,
            text="Feldolgozás és Base64 CSV mentés",
            command=self.process_files,
            state=tk.DISABLED,
            bg="#4CAF50",
            fg="white",
            height=2,
        )
        self.btn_start.pack(pady=10, fill=tk.X, padx=20)

    def log(self, message):
        self.log_area.insert(tk.END, message + "\n")
        self.log_area.see(tk.END)
        self.root.update_idletasks()

    def browse_folder(self):
        """Ez a függvény hiányzott vagy elcsúszott az előbb!"""
        self.selected_path = filedialog.askdirectory()
        if self.selected_path:
            self.lbl_path.config(text=self.selected_path, fg="black")
            self.btn_start.config(state=tk.NORMAL)
            self.log(f"Kiválasztott mappa: {self.selected_path}")

    def manual_dxf_parse(self, dxf_path):
        """Szöveges DXF feldolgozás, ha az ezdxf elakad."""
        points = []
        try:
            with open(dxf_path, "r", encoding="utf-8", errors="ignore") as f:
                content = f.read().splitlines()
            i = 0
            while i < len(content):
                line = content[i].strip()
                if (
                    line == "0"
                    and i + 1 < len(content)
                    and content[i + 1].strip() == "CIRCLE"
                ):
                    current_layer, x, y = "", None, None
                    i += 2
                    while i < len(content) and content[i].strip() != "0":
                        code, val = content[i].strip(), content[i + 1].strip()
                        if code == "8":
                            current_layer = val
                        if code == "10":
                            x = float(val)
                        if code == "20":
                            y = float(val)
                        i += 2
                    if (
                        current_layer.upper() == "LAYER1"
                        and x is not None
                        and y is not None
                    ):
                        points.append((x, y))
                    continue
                i += 1
            return points
        except:
            return None

    def get_layer1_circles(self, dxf_path):
        """Hibrid olvasó: ezdxf -> manual."""
        try:
            doc, auditor = recover.readfile(dxf_path)
            msp = doc.modelspace()
            # Itt egy egyszerűbb lekérdezést használunk a stabilitásért
            pts = []
            for e in msp:
                if e.dxftype() == "CIRCLE" and e.dxf.layer.upper() == "LAYER1":
                    pts.append(e.dxf.center[:2])
            return pts
        except:
            return self.manual_dxf_parse(dxf_path)

    def process_files(self):
        self.log("\n--- FELDOLGOZÁS INDÍTÁSA (Base64 mód) ---")
        results = []
        stats = {"total": 0, "success": 0, "no_grid": 0, "empty": 0}

        for root_dir, dirs, files in os.walk(self.selected_path):
            for file in files:
                if file.lower().endswith(".dxf"):
                    stats["total"] += 1
                    full_path = os.path.join(root_dir, file)
                    circles = self.get_layer1_circles(full_path)

                    if circles is not None and len(circles) > 0:
                        b64_fprint = self.engine.fit_points(circles)
                        if b64_fprint:
                            id_name = os.path.splitext(file)[0]
                            results.append(f"{id_name};{b64_fprint}")
                            self.log(f"OK: {file} -> [ {b64_fprint[:20]}... ]")
                            stats["success"] += 1
                        else:
                            self.log(f"HIBA: {file} -> Nem illeszkedik a rácsra!")
                            stats["no_grid"] += 1
                    else:
                        self.log(f"ÜRES: {file} -> Nincs kör a Layer1-en.")
                        stats["empty"] += 1

        if results:
            csv_path = os.path.join(self.selected_path, "ujjlenyomatok_b64.csv")
            with open(csv_path, "w", encoding="utf-8") as f:
                f.write("\n".join(results))

            self.log(f"\n" + "=" * 40)
            self.log(f"ÖSSZESÍTÉS:")
            self.log(f"  Talált DXF:           {stats['total']}")
            self.log(f"  Sikeres ujjlenyomat:  {stats['success']}")
            self.log(f"  Rossz rács/zaj:       {stats['no_grid']}")
            self.log(f"  Üres fájl:            {stats['empty']}")
            self.log(f"CSV mentve: {csv_path}")
            self.log("=" * 40)
        else:
            self.log("\n--- KÉSZ! (Nem született eredmény) ---")


if __name__ == "__main__":
    root = tk.Tk()
    app = BrailleBulldozer(root)
    root.mainloop()

Ez a program azt tudja, hogy egy kiválasztott könyvtár összes alkönyvtárát bejárva, megkeresi az összes dxf filet, azokban Circle/Spline elemeket keres, azokat megpróbálja megfeleltetni a 20*4 braille-karakteres elméleti pontfelhőnek, majd a talált pozíciókat visszaadva Base64-es ujjlenyomatot készít. A filenév mellett (ami egyben azonosító) az ujjlenyomatokat is egy CSV fileba menti, plusz ad hibalistát azokról a dxf-ekről, amiket nem tudott értelmezni. Ezzel a már létező kliséket adatbázisba tudjuk vinni.

Az ujjlenyomatról:
Itt egy online bináris-Base64 átalakító. Azért a Base64-et választottuk egyéb, rövidebb ujjlenyomatot adó hesseléssekkel szemben, mert jobban átlátjuk a működését, valóban egyedi, másfelől annyira szerencsések vagyunk, hogy a Base64 3 byteos  szegmensei oszthatók 6-tal is, Tehát 24 biten 4 darab, 6 pontos braille-karakter kódolható el. És csodák csodája a 20*4 karakteres 480 pont is osztható 6-tal, meg 24-gyel, szóval ilyen egy szerencsés együttállás. Az ujjlenyomat a braille karakter bal felső pöttyétől halad lefele hármat, majd a karakter jobb felső pozíciójára ugrik, megint lefele halad három pontot, majd ugrik a következő karakter bal felső pontjára, ha elfogyott a 20 karakter, ugrik a következő sor első karakterének első pozíciójára és így tovább. Ahol pontot talál ott 1, ahol nincs pont, ott 0 a visszaadott érték. Tehát így keletkezik egy 480 egybites sorunk, amit 6 bitenként feleltetünk meg a Base64 szótárában levő 2^6 vagyis 64 karakterének. 

Ez egy félkész megfeleltetés, ezt használtuk az ujjlenyomatok ellenőrzésére a tesztfázisban. Természetesen a mi braille-kliséink 6 pontos karakterei nem mindig értelmezhetőek a braille írásnak megfelelően, mert a kliséink normál és 180 fokban fordított állásban is előfordulhatnak, a fordított karakterek vagy teljesen más betűt jelentenek, vagy egyáltalán nem értelmezettek. Így, hogy a vakok 90 százaléka tojik a braille írásra, nincs is értelme braille szöveg-értelmezőt írni. Nekünk az iparban nem fontos mit ír a dobozon, csak hogy minden pont a helyén legyen.

BraiLuca

(a vakok védőszentjéről elnevezve - hogy legyen kulturális adalék is)

# INSUNIT mm-re alllitva, SPLINE es CIRCLE elemeket keres

import os
import tkinter as tk
from tkinter import filedialog, messagebox
import ezdxf
from ezdxf import recover
import math


# --- BRAILLE MOTOR ---
class BrailleEngine:
    def __init__(self):
        self.dot_dist = 2.5
        self.char_dist = 6.0
        self.row_dist = 10.0
        self.tolerance = 0.8  # Kicsit engedékenyebb az új kliséknél
        self.b64_chars = (
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
        )

    def bits_to_base64(self, bit_string):
        b64_result = ""
        for i in range(0, 480, 6):
            six_bits = bit_string[i : i + 6]
            val = 0
            for bit_pos, bit in enumerate(six_bits):
                if bit == "1":
                    val += 1 << bit_pos
            b64_result += self.b64_chars[val]
        return b64_result

    def get_theoretical_grid(self, offset_x=0, offset_y=0):
        """Generál egy rácsot adott eltolással."""
        grid = []
        for row in range(4):
            for char in range(20):
                for dot_col in range(2):
                    for dot_row in range(3):
                        gx = (
                            (char * self.char_dist)
                            + (dot_col * self.dot_dist)
                            + offset_x
                        )
                        gy = (
                            -((row * self.row_dist) + (dot_row * self.dot_dist))
                            + offset_y
                        )
                        grid.append((gx, gy))
        return grid

    def get_best_fit(self, points):
        if not points:
            return None, None, False

        min_x = min(p[0] for p in points)
        max_y = max(p[1] for p in points)

        offsets = [(0, 0), (2.5, 0), (0, 2.5), (2.5, 2.5), (0, 5.0), (2.5, 5.0)]

        for ox, oy in offsets:
            theoretical_grid = self.get_theoretical_grid(min_x - ox, max_y + oy)
            fingerprint_bits = ["0"] * 480
            snapped_points = []
            matched_count = 0

            for px, py in points:
                for i, (gx, gy) in enumerate(theoretical_grid):
                    dist = math.sqrt((px - gx) ** 2 + (py - gy) ** 2)
                    if dist <= self.tolerance:
                        fingerprint_bits[i] = "1"
                        snapped_points.append((gx, gy))
                        matched_count += 1
                        break

            if matched_count == len(points):
                is_vertical_shifted = (oy != 0)
                return self.bits_to_base64("".join(fingerprint_bits)), snapped_points, is_vertical_shifted

        return None, None, False

# --- BRAI LUCA GUI (v1.4 - Spline & Circle Support) ---
class BraiLuca:
    def __init__(self, root):
        self.root = root
        self.root.title("BraiLuca v1.4 - Multi-Entity Support")
        self.root.geometry("500x450")
        self.engine = BrailleEngine()
        self.snapped_points = []
        self.fingerprint = ""

        tk.Label(
            root, text="BraiLuca - Klisé Tisztító", font=("Arial", 12, "bold")
        ).pack(pady=10)

        self.btn_open = tk.Button(
            root, text="DXF Megnyitása", command=self.load_file, height=2, width=30
        )
        self.btn_open.pack(pady=10)

        self.info_frame = tk.LabelFrame(root, text="Státusz", padx=10, pady=10)
        self.info_frame.pack(pady=10, padx=20, fill="x")

        self.lbl_dots = tk.Label(self.info_frame, text="Pontok: -")
        self.lbl_dots.pack(anchor="w")

        self.lbl_fprint = tk.Label(
            self.info_frame, text="Ujjlenyomat: -", font=("Consolas", 9), wraplength=400
        )
        self.lbl_fprint.pack(anchor="w")

        self.btn_copy = tk.Button(
            root,
            text="Ujjlenyomat másolása",
            command=self.copy_to_clipboard,
            state="disabled",
        )
        self.btn_copy.pack(pady=5)

        self.btn_save = tk.Button(
            root,
            text="Tökéletes DXF mentése (Circle)",
            command=self.save_cleaned_dxf,
            state="disabled",
            bg="#4CAF50",
            fg="white",
            height=2,
        )
        self.btn_save.pack(pady=10)

    def get_points_from_entity(self, entity, points_list):
        """Kinyeri a pontokat SPLINE középpontként vagy CIRCLE középpontként."""
        dxftype = entity.dxftype()
       
        # KÖRÖK KEZELÉSE
        if dxftype == "CIRCLE":
            cp = entity.dxf.center
            # Kényszerítsük sima tuple formátumra: (x, y)
            points_list.append((float(cp.x), float(cp.y)))
           
        # SPLINE-OK KEZELÉSE
        elif dxftype == "SPLINE":
            cp = entity.control_points
            if cp:
                # Itt már eleve tuple-t generálsz, ez rendben van
                avg_x = sum(p[0] for p in cp) / len(cp)
                avg_y = sum(p[1] for p in cp) / len(cp)
                points_list.append((avg_x, avg_y))
               
        # BLOKKOK KEZELÉSE
        elif dxftype == "INSERT":
            # A virtual_entities() jó megoldás a beágyazott elemekre
            for sub_entity in entity.virtual_entities():
                self.get_points_from_entity(sub_entity, points_list)

    def load_file(self):
        path = filedialog.askopenfilename(filetypes=[("DXF fájlok", "*.dxf")])
        if not path:
            return

        filename = os.path.basename(path)

        try:
            doc = ezdxf.readfile(path)
            msp = doc.modelspace()
            all_raw_points = []

            # Minden entitást megvizsgálunk (Spline és Circle is bekerül)
            for entity in msp:
                self.get_points_from_entity(entity, all_raw_points)

            if not all_raw_points:
                messagebox.showwarning("Figyelem", "Nem találtam sem SPLINE, sem CIRCLE elemet a fájlban!")
                return

            # --- DEDUPLIKÁCIÓ ---
            unique_points = []
            dup_count = 0
            for p in all_raw_points:
                is_duplicate = False
                for u in unique_points:
                    if math.dist(p, u) < 0.1:
                        is_duplicate = True
                        break
                if is_duplicate:
                    dup_count += 1
                else:
                    unique_points.append(p)

            status_text = f"Fájl: {filename}\nPontok: {len(unique_points)} db"
            if dup_count > 0:
                status_text += f" ({dup_count} duplikátum törölve)"
            self.lbl_dots.config(text=status_text, fg="black")

            fprint, snapped, is_shifted = self.engine.get_best_fit(unique_points)

            if fprint:
                self.fingerprint = fprint
                self.snapped_points = snapped
               
                if is_shifted:
                    display_text = f"Ujjlenyomat: {self.fingerprint}\n⚠️ FÜGGŐLEGES ELTOLÁS! Célgépen kézi bevitel szükséges!"
                    self.lbl_fprint.config(text=display_text, fg="red")
                else:
                    self.lbl_fprint.config(text=f"Ujjlenyomat: {self.fingerprint}", fg="black")
               
                self.btn_copy.config(state="normal")
                self.btn_save.config(state="normal")
            else:
                self.lbl_fprint.config(text="HIBA: Nem illeszkedik a rácsra!", fg="red")
                self.btn_save.config(state="disabled")

        except Exception as e:
            messagebox.showerror("Hiba", f"Beolvasási hiba: {str(e)}")

    def copy_to_clipboard(self):
        self.root.clipboard_clear()
        self.root.clipboard_append(self.fingerprint)
        messagebox.showinfo("Siker", "Vágólapra másolva!")

    def save_cleaned_dxf(self):
        path = filedialog.asksaveasfilename(
            defaultextension=".dxf", filetypes=[("DXF fájlok", "*.dxf")]
        )
        if not path:
            return

        new_doc = ezdxf.new("R2000")
       
        try:
            new_doc.header['$INSUNITS'] = 4
            new_doc.header['$MEASUREMENT'] = 1
        except:
            try:
                new_doc.header.set('$INSUNIT', 4)
            except:
                pass
       
        new_doc.layers.new(name="Layer1", dxfattribs={"color": 7})
        msp = new_doc.modelspace()

        for p in self.snapped_points:
            msp.add_circle(p, radius=0.8, dxfattribs={"layer": "Layer1"})

        new_doc.saveas(path)
        messagebox.showinfo("Siker", "A klisé tisztítva és körökké alakítva mentve!")


if __name__ == "__main__":
    root = tk.Tk()
    app = BraiLuca(root)
    root.mainloop()

Ez a progi a kiválasztott dxf filet értelmezi, Spline és Circle elemeket keres benne (mivel a régebbi Illustrator és a CorelDraw nem tud Circle elemeket exportálni, viszont az újabb Illustrátorok már csak ezt tudnak), ezeket felfeszíti a képzeletbeli 480 pontos gridre, ha kell (mert például a legbaloldalibb karakter bal sorában nincs egyetlen pont se, vagy a legfölső sor legfelső pontsorában sincs) akkor addig offseteli, amíg minden Spline egybeesik a képzeletbeli grid megfelelő pontjaival (minimális tolerancián belül) Ezeknek ujjlenyomatot gyárt (Base64), amire rá tudok keresni az adatbázisunkban, hogy nem e létezik már ugyanilyen klisé. Ha nem találtunk egyezőséget (a másik programban), a program engedi elmenteni a dxf-et új néven, amiben kitakarítja a Splineokat (akkor is ha egymáson több van) és Circle elemekre cseréli a Layer1 rétegre, mert ezt keresi a CNC-gép. Továbbá riportot ad, a pontok számáról, illetve ha függőleges offsetre került sor, ugyanis az acélklisé gyártó szoftver nem tud offsetelni, az olyan kliséket a saját terminálján kell begépelni.

BrAlajos 

import os
import re
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
import ezdxf
from ezdxf import recover
import math
import traceback


# --- 1. MOTOR ---
class AlajosEngine:
    def __init__(self):
        self.tolerance = 0.8

    def get_grid_info(self, points):
        if not points:
            return None
        grid_base = []
        for r in range(4):
            for c in range(20):
                for dc in range(2):
                    for dr in range(3):
                        grid_base.append(
                            (float(c * 6.0 + dc * 2.5), float(-(r * 10.0 + dr * 2.5)))
                        )

        min_x, max_x = float(min(p[0] for p in points)), float(
            max(p[0] for p in points)
        )
        max_y = float(max(p[1] for p in points))

        for ox, oy in [(0, 0), (2.5, 0)]:
            matched = 0
            for px, py in points:
                tx, ty = float(px - min_x + ox), float(py - max_y + oy)
                for gx, gy in grid_base:
                    if math.sqrt((tx - gx) ** 2 + (ty - gy) ** 2) <= self.tolerance:
                        matched += 1
                        break
            if matched == len(points):
                return {
                    "ref_x": float(min_x - ox),
                    "ref_y": float(max_y - oy),
                    "max_x": float(max_x),
                    "min_x_orig": min_x,
                    "max_y_orig": max_y,
                }
        return None


# --- 2. GUI ---
class BrAlajos:
    def __init__(self, root):
        self.root = root
        self.root.title("BrAlajos v1.7.1 - Ultimate Hybrid")
        self.root.geometry("600x750")
        self.engine = AlajosEngine()
        self.current_dxf = ""
        self.grid_data = None
        self.snapped_points = []

        tk.Label(root, text="BrAlajos - Finalizáló", font=("Arial", 12, "bold")).pack(
            pady=10
        )
        btn_frame = tk.Frame(root)
        btn_frame.pack(fill="x", padx=20)
        tk.Button(
            btn_frame, text="DXF Megnyitása", command=self.load_dxf, height=2
        ).pack(side="left", expand=True, fill="x")
        tk.Button(
            btn_frame,
            text="Vágólap Beolvasása",
            command=self.parse_clipboard,
            height=2,
            bg="#e1f5fe",
        ).pack(side="left", expand=True, fill="x")

        self.fields = []
        labels = [
            "1. Filenév maradvány:",
            "2. St: azonosító:",
            "3. Terméknév (- C.):",
            "4. BT dátum:",
        ]
        for i in range(4):
            f_frame = tk.Frame(root)
            f_frame.pack(fill="x", padx=20, pady=5)
            tk.Label(f_frame, text=labels[i], width=20, anchor="w").pack(side="left")
            ent = tk.Entry(f_frame, font=("Arial", 10))
            ent.pack(side="right", expand=True, fill="x")
            self.fields.append(ent)

        self.info_area = scrolledtext.ScrolledText(
            root, height=15, font=("Consolas", 9), bg="#f0f0f0"
        )
        self.info_area.pack(pady=10, padx=20, fill="both")
        self.btn_save = tk.Button(
            root,
            text="DXF FINALIZÁLÁSA",
            command=self.save_dxf,
            state="disabled",
            bg="#4CAF50",
            fg="white",
            height=2,
        )
        self.btn_save.pack(pady=10, fill="x", padx=20)

    def is_pac_mode(self, path):
        """Központosított PAC ellenőrzés: névben VAGY végződésben."""
        name = os.path.basename(path).upper()
        # Igaz, ha benne van a -PAC- VAGY a kiterjesztés előtt -PAC van
        return "-PAC-" in name or name.replace(".DXF", "").endswith("-PAC")

    def log(self, msg, color="black"):
        tag = f"tag_{color}"
        self.info_area.tag_configure(tag, foreground=color)
        self.info_area.insert(tk.END, msg + "\n", tag)
        self.info_area.see(tk.END)
        self.root.update()

    def load_dxf(self):
        path = filedialog.askopenfilename(filetypes=[("DXF fájlok", "*.dxf")])
        if not path:
            return
        self.current_dxf = path
        self.btn_save.config(state="disabled")
        try:
            try:
                doc, auditor = recover.readfile(path)
            except:
                doc = ezdxf.readfile(path)
            msp = doc.modelspace()
            circles = [
                (float(e.dxf.center.x), float(e.dxf.center.y))
                for e in msp
                if e.dxftype() == "CIRCLE" and e.dxf.layer.upper() == "LAYER1"
            ]
            if not circles:
                self.log("HIBA: Layer1 üres!", "red")
                return

            self.snapped_points = circles
            self.grid_data = self.engine.get_grid_info(circles)

            if self.grid_data:
                mode_str = "PAC-MÓD" if self.is_pac_mode(path) else "NORMÁL-MÓD"
                self.log(f"Fájl: {os.path.basename(path)}\nMód: {mode_str}")
                self.btn_save.config(state="normal")
                parts = os.path.basename(path).replace(".dxf", "").split("-", 1)
                if len(parts) > 1:
                    self.fields[0].delete(0, tk.END)
                    self.fields[0].insert(0, parts[1])
                    self.fields[1].delete(0, tk.END)
                    self.fields[1].insert(0, f"St: {parts[0]}")
            else:
                self.log("HIBA: Nem rács-szerkezet!", "red")
        except:
            self.log(traceback.format_exc(), "red")

    def parse_clipboard(self):
        try:
            text = self.root.clipboard_get()
            if "- C." in text:
                part = text.split("- C.")[1].strip().split("\n")[0]
                self.fields[2].delete(0, tk.END)
                self.fields[2].insert(0, part[:15])
            date_match = re.search(r"BT:\s*(\d{2}\.\d{2}\.\d{4})", text)
            if date_match:
                self.fields[3].delete(0, tk.END)
                self.fields[3].insert(0, f"BT: {date_match.group(1)}")
            self.log("Vágólap OK.")
        except:
            self.log("Vágólap hiba.")

    def find_text_space(self, points, box_w, box_h):
        min_x = self.grid_data["min_x_orig"]
        right_limit = self.grid_data["max_x"] + 5.0
        row_coords = sorted(list(set(round(p[1], 1) for p in points)), reverse=True)
        base_y = row_coords[0] if row_coords else self.grid_data["max_y_orig"]
        y_corridors = [base_y - (i * 10.0) - 5.0 for i in range(4)]

        curr_x = min_x - 3.0
        while (curr_x + box_w) <= right_limit:
            for mid_y in y_corridors:
                top, bottom = mid_y + (box_h / 2), mid_y - (box_h / 2)
                collision = any(
                    curr_x <= p[0] <= curr_x + box_w and bottom <= p[1] <= top
                    for p in points
                )
                if not collision:
                    return (curr_x, mid_y + (box_h / 2))
            curr_x += 2.0
        return None

    def save_dxf(self):
        if not self.current_dxf or not self.grid_data:
            return
        try:
            doc = ezdxf.readfile(self.current_dxf)
            doc.header["$DWGCODEPAGE"] = "UTF-8"
            msp = doc.modelspace()

            is_pac = self.is_pac_mode(self.current_dxf)
            y_top_pac = float(self.grid_data["max_y_orig"] + 4.0)

            for i in range(4):
                val = self.fields[i].get().strip()
                if not val:
                    continue
                box_w, box_h = (len(val) * 2.75) + 3.0, 8.0

                if is_pac:
                    space = self.find_text_space(self.snapped_points, box_w, box_h)
                    if space:
                        tx, ty = space
                        final_pos = (float(tx + 1.5), float(ty - box_h + 1.5))
                    else:
                        # Mentőöv: Felső vonalra pakolás eltolva
                        self.log(f"Vigyázat: '{val}' a keret fölé került.", "orange")
                        final_pos = (
                            float(self.grid_data["min_x_orig"] + (i * 35)),
                            float(y_top_pac + 1.0),
                        )
                else:
                    # NORMÁL MÓD: Fix pozíciók
                    rx, ry = self.grid_data["ref_x"], self.grid_data["ref_y"]
                    positions = [
                        (rx + 364, ry - 10),
                        (rx + 364, ry - 20),
                        (rx + 364, ry - 30),
                        (rx + 364, ry - 40),
                    ]
                    final_pos = positions[i]

                t = msp.add_text(val, dxfattribs={"layer": "Layer1", "height": 3.5})
                t.set_placement(final_pos)

            if is_pac:
                x_s, x_e = float(self.grid_data["min_x_orig"] - 3.0), float(
                    self.grid_data["max_x"] + 5.0
                )
                for y in [y_top_pac, y_top_pac - 42.9]:
                    msp.add_line((x_s, y), (x_e, y), dxfattribs={"layer": "Layer1"})

            doc.saveas(self.current_dxf, encoding="utf-8")
            self.log("Sikeres mentés.")
            messagebox.showinfo("Kész", "DXF frissítve!")
            self.btn_save.config(state="disabled")
        except Exception as e:
            self.log(f"Hiba: {str(e)}", "red")


if __name__ == "__main__":
    root = tk.Tk()
    app = BrAlajos(root)
    root.mainloop()

Ez a progi a BraiLuca által kicsinosított dxf filet készíti elő véglegesre. A filenévből (ami az azonosító is) illetve vágólapról kapott metaadattal felcímkézi a dxf-et. Négy editálható szövegdobozba gyűjti ki az adatokat, ezek utólag szerkeszthetőek, ha szükség lenne rá, illetve mentésnél a megfelelő pozícióba írja ezeket a dxf-be. A célprogram kétféle módot képes kezelni, ezért a BrAlajos is megkülönbözteti ezeket.

# NORMÁL MÓD: Fix pozíciók
                    rx, ry = self.grid_data["ref_x"], self.grid_data["ref_y"]
                    positions = [
                        (rx + 364, ry - 10),
                        (rx + 364, ry - 20),
                        (rx + 364, ry - 30),
                        (rx + 364, ry - 40),
                    ]
                    final_pos = positions[i]

A normál módot a célgép úgy értelmezi, hogy egy hosszú, fix méretű acéllemez-abroncsra kétszer teszi rá a pontfelhőt és a klisé végére marja az azonosítókat. Mellesleg rotációs torzítást is alkalmaz.

A kivételes módban a klisé szélességét két vízszintes vonallal kell a dxf-ben reprezentálni, a klisé hossza pedig a pontfelhő hosszától dinamikusan függ. Ezeket a vonalakat is berajzolja a program, illetve szövegelemnek (hosszát a karakterek számából saccolja meg), helyet keres a klisé mind a négy sorában. Ha nem talál, figyelmeztet, kézi beavatkozás szükségességére.

Az ujjlenyomatkezelő

import tkinter as tk
from tkinter import messagebox, scrolledtext, filedialog
import sqlite3
import csv

# --- KONFIGURÁCIÓ ---
# Ha hálózati meghajtón használjátok, ide írd az elérést, pl: "Z:/adatok/ujjlenyomatok.db"
DB_PATH = "Z:\Temp\!!!INSTALL\Makrok_Hasznos_Kiskesek\BraiLucaBrAlajos\ujjlenyomatok.db"


def init_db():
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute(
        """CREATE TABLE IF NOT EXISTS fingerprints
                 (identifier TEXT PRIMARY KEY, fingerprint TEXT)"""
    )
    conn.commit()
    conn.close()


def import_csv_to_db():
    file_path = filedialog.askopenfilename(filetypes=[("CSV fájlok", "*.csv")])
    if not file_path:
        return
    if not messagebox.askyesno(
        "Megerősítés", "Töröljek minden adatot és betöltsem a CSV-t?"
    ):
        return

    try:
        conn = sqlite3.connect(DB_PATH)
        c = conn.cursor()
        c.execute("DELETE FROM fingerprints")
        with open(file_path, mode="r", encoding="utf-8-sig") as f:
            reader = csv.reader(f, delimiter=";")
            count = 0
            for row in reader:
                if len(row) >= 2:
                    c.execute(
                        "INSERT OR REPLACE INTO fingerprints (identifier, fingerprint) VALUES (?, ?)",
                        (row[0].strip(), row[1].strip()),
                    )
                    count += 1
        conn.commit()
        conn.close()
        messagebox.showinfo("Siker", f"Beöltött sorok: {count}")
    except Exception as e:
        messagebox.showerror("Hiba", str(e))


def search_fingerprint():
    fp = entry_fp.get().strip()
    if len(fp) != 80:
        messagebox.showwarning("Figyelem", "Az ujjlenyomat 80 karakter kell legyen!")
        return

    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("SELECT identifier FROM fingerprints WHERE fingerprint = ?", (fp,))
    results = c.fetchall()
    conn.close()

    entry_res.config(state="normal")
    entry_res.delete(0, tk.END)

    if results:
        ids = ", ".join([r[0] for r in results])
        entry_res.insert(0, ids)
        # Nem szürkítjük ki a gombot, mert lehet, hogy új azonosítóval is felvinnék
    else:
        entry_res.insert(0, "NINCS MÉG ILYEN UJJLENYOMAT")

    entry_res.config(state="readonly")


def save_new():
    fp = entry_fp.get().strip()
    new_id = entry_new_id.get().strip()

    if not new_id or len(fp) != 80:
        messagebox.showerror("Hiba", "Hiányzó azonosító vagy rossz ujjlenyomat hossz!")
        return

    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    try:
        # Ellenőrizzük, hogy ez az ujjlenyomat már létezik-e más néven (csak tájékoztatás)
        c.execute("SELECT identifier FROM fingerprints WHERE fingerprint = ?", (fp,))
        exists = c.fetchall()

        if exists:
            msg = f"Ez az ujjlenyomat már szerepel a következő azonosítókkal: {', '.join([x[0] for x in exists])}\n\nBiztosan felveszed új azonosítóval ({new_id}) is?"
            if not messagebox.askyesno("Duplikátum figyelmeztetés", msg):
                return

        c.execute(
            "INSERT INTO fingerprints (identifier, fingerprint) VALUES (?, ?)",
            (new_id, fp),
        )
        conn.commit()
        messagebox.showinfo("Siker", "Adat elmentve!")
        entry_new_id.delete(0, tk.END)
        search_fingerprint()  # Frissítjük a nézetet
    except sqlite3.IntegrityError:
        messagebox.showerror(
            "Hiba",
            "Ez az AZONOSÍTÓ már foglalt! Minden ujjlenyomatnak egyedi azonosító kell.",
        )
    finally:
        conn.close()


def check_duplicates():
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    query = """SELECT fingerprint, GROUP_CONCAT(identifier, ' | ')
               FROM fingerprints
               GROUP BY fingerprint
               HAVING COUNT(identifier) > 1"""
    c.execute(query)
    dupes = c.fetchall()
    conn.close()

    top = tk.Toplevel(root)
    top.title("Duplikátumok")
    txt = scrolledtext.ScrolledText(top, width=100, height=20)
    txt.pack(padx=10, pady=10)
    if not dupes:
        txt.insert(tk.END, "Nincs duplikált ujjlenyomat.")
    else:
        for f, ids in dupes:
            txt.insert(tk.END, f"{f} -> {ids}\n\n")


# --- UI ---
root = tk.Tk()
root.title("Ujjlenyomat Kezelő")
root.geometry("700x500")

# Keresés
tk.Label(root, text="Ujjlenyomat (80 karakter):").pack(pady=5)
entry_fp = tk.Entry(root, width=90)
entry_fp.pack(pady=5)
tk.Button(root, text="ELLENŐRZÉS", command=search_fingerprint, bg="#d1e7ff").pack(
    pady=5
)

# Eredmény
tk.Label(root, text="Jelenlegi azonosítók az adatbázisban:").pack(pady=5)
entry_res = tk.Entry(root, width=70, font=("Arial", 10, "bold"), justify="center")
entry_res.config(state="readonly")
entry_res.pack(pady=5)

# Mentés
tk.Frame(root, height=2, bd=1, relief="sunken").pack(fill="x", padx=20, pady=10)
tk.Label(root, text="Új azonosító rögzítése (ha szükséges):").pack(pady=5)
entry_new_id = tk.Entry(root, width=40)
entry_new_id.pack(pady=5)
tk.Button(root, text="MENTÉS ÚJ AZONOSÍTÓKÉNT", command=save_new, bg="#d4edda").pack(
    pady=5
)

# Admin
admin_frame = tk.Frame(root)
admin_frame.pack(side="bottom", fill="x", pady=20)
tk.Button(admin_frame, text="Duplikátum riport", command=check_duplicates).pack(
    side="left", padx=20
)
tk.Button(admin_frame, text="CSV Import", command=import_csv_to_db, bg="#f8d7da").pack(
    side="right", padx=20
)

init_db()
root.mainloop()

SQlite adatbázist használ. Ez a program vágólapon fogadja a BraiLuca által generált ujjlenyomatot és egyezőséget keres a már létező és a Bulldózer által készített adatbázisban. Többlépcsős figyelmeztetés után engedi az adatbázis újratöltését a megadott CSV-ből (amennyiben valami adatveszteség, vagy legelső használat miatt be kellene dózerolni a Bulldózerrel az összes létező klisét). Ugyanazt az ujjlenyomatot több kódnéven is engedi elmenteni (praktikus szükségesség a rendszerünkben, hogy ugyanaz a pontfelhő több típusú klisén is szerepelhet), találat esetén minden egyezőséget megmutat. Sőt képes az adatbázisban duplikátumok listázására is, amivel első körben arra voltunk kíváncsiak, hogy a régi nyögvenyelős azonosságkereső módszerünk mennyire volt megbízható. 
Ezt a progit nem dolgoztuk ki túlságosan, mert az ujjlenyomatok kezelését várhatóan a céges PHP rendszerbe integrálnánk később, csupán a teszt-időszakra volt rá szükségünk. 

Az ujjlenyomatot is kezelő BraiLuca

Mivel nagyon hosszas és beszédes hallgatást tapasztaltunk a vezetőség oldaláról, ezért elengedtük azt a lehetőséget, hogy az ujjlenyomatkezelést a céges PHP-ba implementáljuk. Ezért a BraiLucába belekerült az Ujjlenyomatkezelő néhány funkciója, az azonosítás és az adatbázisba mentés. Így nincs szükség külön program elindítására. A duplikátumkezelést, CSV importot nem tartottuk meg, mert minek, illetve az adatbázis szerkesztgetését (például elgépelési hibák javítása), egy külső ingyenes adatbáziskezelővel  (SQLiteDatabaseBrowserPortable) oldjuk majd meg. 
A módosítást ezennel a Claude AI végezte, második nekifutásra jó is lett. Sőt cuki volt, mert az ikon-mizériát (ha nincs az exe mellett crashel a program) is megoldotta csendben (try: except:). Ezt alkalomadtán a BrAlajosba is bele kellene kompillálni. 

# BraiLuca v11 - Spline & Circle vadász + Ujjlenyomat adatbázis
# Egyesített verzió: BraiLuca v8 + Ujjlenyomatkereső funkciói

import os
import tkinter as tk
from tkinter import filedialog, messagebox
import ezdxf
import math
import sqlite3

# --- KONFIGURÁCIÓ ---
DB_PATH = (
    r"Z:\Temp\!!!INSTALL\Makrok_Hasznos_Kiskesek\BraiLucaBrAlajos\ujjlenyomatok.db"
)


# --- ADATBÁZIS INICIALIZÁLÁS ---
def init_db():
    try:
        conn = sqlite3.connect(DB_PATH)
        c = conn.cursor()
        c.execute("""CREATE TABLE IF NOT EXISTS fingerprints
                     (identifier TEXT PRIMARY KEY, fingerprint TEXT)""")
        conn.commit()
        conn.close()
    except Exception as e:
        # Ha az adatbázis nem elérhető, csak figyelmeztetünk, nem állunk le
        print(f"DB inicializálási hiba: {e}")


# --- BRAILLE MOTOR (VÁLTOZATLAN) ---
class BrailleEngine:
    def __init__(self):
        self.dot_dist = 2.5
        self.char_dist = 6.0
        self.row_dist = 10.0
        self.tolerance = 0.8
        self.b64_chars = (
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
        )

    def bits_to_base64(self, bit_string):
        b64_result = ""
        for i in range(0, 480, 6):
            six_bits = bit_string[i : i + 6]
            val = 0
            for bit_pos, bit in enumerate(six_bits):
                if bit == "1":
                    val += 1 << bit_pos
            b64_result += self.b64_chars[val]
        return b64_result

    def get_theoretical_grid(self, offset_x=0, offset_y=0):
        grid = []
        for row in range(4):
            for char in range(20):
                for dot_col in range(2):
                    for dot_row in range(3):
                        gx = (
                            (char * self.char_dist)
                            + (dot_col * self.dot_dist)
                            + offset_x
                        )
                        gy = (
                            -((row * self.row_dist) + (dot_row * self.dot_dist))
                            + offset_y
                        )
                        grid.append((gx, gy))
        return grid

    def get_best_fit(self, points):
        if not points:
            return None, None, False

        min_x = min(p[0] for p in points)
        max_y = max(p[1] for p in points)

        offsets = [(0, 0), (2.5, 0), (0, 2.5), (2.5, 2.5), (0, 5.0), (2.5, 5.0)]

        for ox, oy in offsets:
            theoretical_grid = self.get_theoretical_grid(min_x - ox, max_y + oy)
            fingerprint_bits = ["0"] * 480
            snapped_points = []
            matched_count = 0

            for px, py in points:
                for i, (gx, gy) in enumerate(theoretical_grid):
                    dist = math.sqrt((px - gx) ** 2 + (py - gy) ** 2)
                    if dist <= self.tolerance:
                        fingerprint_bits[i] = "1"
                        snapped_points.append((gx, gy))
                        matched_count += 1
                        break

            if matched_count == len(points):
                is_vertical_shifted = oy != 0
                return (
                    self.bits_to_base64("".join(fingerprint_bits)),
                    snapped_points,
                    is_vertical_shifted,
                )

        return None, None, False


# --- EGYESÍTETT GUI ---
class BraiLuca:
    def __init__(self, root):
        self.root = root
        self.root.title("BraiLuca v11 - Klisé Tisztító & Ujjlenyomat Kezelő")
        self.root.geometry("520x620")
        self.engine = BrailleEngine()
        self.snapped_points = []
        self.fingerprint = ""

        # ── Fejléc ──────────────────────────────────────────────────
        tk.Label(
            root, text="BraiLuca - Klisé Tisztító", font=("Arial", 12, "bold")
        ).pack(pady=8)

        # ── DXF megnyitás ────────────────────────────────────────────
        self.btn_open = tk.Button(
            root, text="DXF Megnyitása", command=self.load_file, height=2, width=30
        )
        self.btn_open.pack(pady=8)

        # ── Státusz keret ────────────────────────────────────────────
        self.info_frame = tk.LabelFrame(root, text="Státusz", padx=10, pady=6)
        self.info_frame.pack(pady=6, padx=20, fill="x")

        self.lbl_dots = tk.Label(self.info_frame, text="Pontok: -")
        self.lbl_dots.pack(anchor="w")

        self.lbl_fprint = tk.Label(
            self.info_frame,
            text="Ujjlenyomat: -",
            font=("Consolas", 9),
            wraplength=440,
        )
        self.lbl_fprint.pack(anchor="w")

        # Ujjlenyomat vágólapra gomb (megmarad)
        self.btn_copy = tk.Button(
            root,
            text="Ujjlenyomat másolása vágólapra",
            command=self.copy_to_clipboard,
            state="disabled",
        )
        self.btn_copy.pack(pady=4)

        # ── Adatbázis találat keret ──────────────────────────────────
        db_frame = tk.LabelFrame(root, text="Adatbázis találat", padx=10, pady=6)
        db_frame.pack(pady=6, padx=20, fill="x")

        self.entry_res = tk.Entry(
            db_frame,
            width=60,
            font=("Arial", 10, "bold"),
            justify="center",
            state="readonly",
        )
        self.entry_res.pack(pady=4)

        # ── Új azonosító rögzítése ───────────────────────────────────
        save_frame = tk.LabelFrame(root, text="Új azonosító rögzítése", padx=10, pady=6)
        save_frame.pack(pady=6, padx=20, fill="x")

        id_row = tk.Frame(save_frame)
        id_row.pack(fill="x")
        tk.Label(id_row, text="Azonosító:").pack(side="left")
        self.entry_new_id = tk.Entry(id_row, width=35)
        self.entry_new_id.pack(side="left", padx=6)

        self.btn_save_db = tk.Button(
            save_frame,
            text="MENTÉS ÚJ AZONOSÍTÓKÉNT",
            command=self.save_new_to_db,
            bg="#d4edda",
            state="disabled",
        )
        self.btn_save_db.pack(pady=4)

        # ── DXF mentés ───────────────────────────────────────────────
        self.btn_save_dxf = tk.Button(
            root,
            text="Tökéletes DXF mentése (Circle)",
            command=self.save_cleaned_dxf,
            state="disabled",
            bg="#4CAF50",
            fg="white",
            height=2,
        )
        self.btn_save_dxf.pack(pady=10)

    # ── SEGÉDFÜGGVÉNYEK (VÁLTOZATLAN) ───────────────────────────────

    def get_points_from_entity(self, entity, points_list):
        dxftype = entity.dxftype()

        if dxftype == "CIRCLE":
            cp = entity.dxf.center
            points_list.append((float(cp.x), float(cp.y)))

        elif dxftype == "SPLINE":
            cp = entity.control_points
            if cp:
                avg_x = sum(p[0] for p in cp) / len(cp)
                avg_y = sum(p[1] for p in cp) / len(cp)
                points_list.append((avg_x, avg_y))

        elif dxftype == "INSERT":
            for sub_entity in entity.virtual_entities():
                self.get_points_from_entity(sub_entity, points_list)

    # ── FÁJL BETÖLTÉS (logika változatlan, + auto DB lekérdezés) ────

    def load_file(self):
        path = filedialog.askopenfilename(filetypes=[("DXF fájlok", "*.dxf")])
        if not path:
            return

        filename = os.path.basename(path)

        try:
            doc = ezdxf.readfile(path)
            msp = doc.modelspace()
            all_raw_points = []

            for entity in msp:
                self.get_points_from_entity(entity, all_raw_points)

            if not all_raw_points:
                messagebox.showwarning(
                    "Figyelem", "Nem találtam sem SPLINE, sem CIRCLE elemet a fájlban!"
                )
                return

            # Deduplikáció (változatlan)
            unique_points = []
            dup_count = 0
            for p in all_raw_points:
                is_duplicate = False
                for u in unique_points:
                    if math.dist(p, u) < 0.1:
                        is_duplicate = True
                        break
                if is_duplicate:
                    dup_count += 1
                else:
                    unique_points.append(p)

            status_text = f"Fájl: {filename}\nPontok: {len(unique_points)} db"
            if dup_count > 0:
                status_text += f" ({dup_count} duplikátum törölve)"
            self.lbl_dots.config(text=status_text, fg="black")

            fprint, snapped, is_shifted = self.engine.get_best_fit(unique_points)

            if fprint:
                self.fingerprint = fprint
                self.snapped_points = snapped

                if is_shifted:
                    display_text = f"Ujjlenyomat: {self.fingerprint}\n⚠️ FÜGGŐLEGES ELTOLÁS! Célgépen kézi bevitel szükséges!"
                    self.lbl_fprint.config(text=display_text, fg="red")
                else:
                    self.lbl_fprint.config(
                        text=f"Ujjlenyomat: {self.fingerprint}", fg="black"
                    )

                self.btn_copy.config(state="normal")
                self.btn_save_dxf.config(state="normal")
                self.btn_save_db.config(state="normal")

                # ── ÚJ: automatikus DB lekérdezés ──
                self._search_in_db(self.fingerprint)

            else:
                self.lbl_fprint.config(text="HIBA: Nem illeszkedik a rácsra!", fg="red")
                self.btn_save_dxf.config(state="disabled")
                self.btn_save_db.config(state="disabled")
                self._clear_db_result()

        except Exception as e:
            messagebox.showerror("Hiba", f"Beolvasási hiba: {str(e)}")

    # ── VÁGÓLAP (VÁLTOZATLAN) ────────────────────────────────────────

    def copy_to_clipboard(self):
        self.root.clipboard_clear()
        self.root.clipboard_append(self.fingerprint)
        messagebox.showinfo("Siker", "Vágólapra másolva!")

    # ── DB LEKÉRDEZÉS (a második programból áthozva, metódusba szervezve) ──

    def _clear_db_result(self):
        self.entry_res.config(state="normal")
        self.entry_res.delete(0, tk.END)
        self.entry_res.config(state="readonly")

    def _search_in_db(self, fingerprint):
        """Lekérdezi az ujjlenyomatot az adatbázisból és megjeleníti az eredményt."""
        self._clear_db_result()
        try:
            conn = sqlite3.connect(DB_PATH)
            c = conn.cursor()
            c.execute(
                "SELECT identifier FROM fingerprints WHERE fingerprint = ?",
                (fingerprint,),
            )
            results = c.fetchall()
            conn.close()

            self.entry_res.config(state="normal")
            if results:
                ids = ", ".join([r[0] for r in results])
                self.entry_res.insert(0, ids)
                self.entry_res.config(fg="navy")
            else:
                self.entry_res.insert(0, "NINCS MÉG ILYEN UJJLENYOMAT")
                self.entry_res.config(fg="gray")
            self.entry_res.config(state="readonly")

        except Exception as e:
            self.entry_res.config(state="normal")
            self.entry_res.insert(0, f"DB hiba: {e}")
            self.entry_res.config(state="readonly", fg="red")

    # ── DB MENTÉS (a második programból áthozva) ─────────────────────

    def save_new_to_db(self):
        """Elmenti az ujjlenyomatot az adatbázisba az új azonosítóval.
        Visszatér True-val ha sikeres, False-szal ha nem."""
        fp = self.fingerprint
        new_id = self.entry_new_id.get().strip()

        if not new_id:
            messagebox.showerror("Hiba", "Kérlek add meg az azonosítót!")
            return False
        if len(fp) != 80:
            messagebox.showerror("Hiba", "Érvénytelen ujjlenyomat (nem 80 karakter)!")
            return False

        try:
            conn = sqlite3.connect(DB_PATH)
            c = conn.cursor()

            # Duplikátum figyelmeztetés (ha ez az ujjlenyomat már létezik)
            c.execute(
                "SELECT identifier FROM fingerprints WHERE fingerprint = ?", (fp,)
            )
            exists = c.fetchall()
            if exists:
                msg = (
                    f"Ez az ujjlenyomat már szerepel a következő azonosítókkal: "
                    f"{', '.join([x[0] for x in exists])}\n\n"
                    f"Biztosan felveszed új azonosítóval ({new_id}) is?"
                )
                if not messagebox.askyesno("Duplikátum figyelmeztetés", msg):
                    conn.close()
                    return False

            c.execute(
                "INSERT INTO fingerprints (identifier, fingerprint) VALUES (?, ?)",
                (new_id, fp),
            )
            conn.commit()
            conn.close()

            messagebox.showinfo("Siker", f"'{new_id}' elmentve az adatbázisba!")
            self._search_in_db(fp)  # Frissítjük a találat mezőt
            return True

        except sqlite3.IntegrityError:
            messagebox.showerror(
                "Hiba",
                "Ez az AZONOSÍTÓ már foglalt! Minden ujjlenyomatnak egyedi azonosító kell.",
            )
            return False
        except Exception as e:
            messagebox.showerror("Hiba", f"DB mentési hiba: {str(e)}")
            return False

    # ── DXF MENTÉS (Circle logika változatlan, mentési rész bővítve) ─

    def save_cleaned_dxf(self):
        path = filedialog.asksaveasfilename(
            defaultextension=".dxf", filetypes=[("DXF fájlok", "*.dxf")]
        )
        if not path:
            return

        new_doc = ezdxf.new("R2000")

        try:
            new_doc.header["$INSUNITS"] = 4
            new_doc.header["$MEASUREMENT"] = 1
        except:
            try:
                new_doc.header.set("$INSUNIT", 4)
            except:
                pass

        new_doc.layers.new(name="Layer1", dxfattribs={"color": 7})
        msp = new_doc.modelspace()

        for p in self.snapped_points:
            msp.add_circle(p, radius=0.8, dxfattribs={"layer": "Layer1"})

        new_doc.saveas(path)
        messagebox.showinfo("Siker", "A klisé tisztítva és körökké alakítva mentve!")


# ── BELÉPÉSI PONT ────────────────────────────────────────────────────

if __name__ == "__main__":
    init_db()
    root = tk.Tk()
    try:
        root.iconbitmap("BrLu.ico")
    except:
        pass  # Ha nincs ico fájl, csendben továbblépünk
    app = BraiLuca(root)
    root.mainloop()



Nincsenek megjegyzések:

Megjegyzés küldése