<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title><![CDATA[QElectroTech — script auto numerotation des bornes]]></title>
		<link>https://qelectrotech.org/forum/viewtopic.php?id=3108</link>
		<atom:link href="https://qelectrotech.org/forum/extern.php?action=feed&amp;tid=3108&amp;type=rss" rel="self" type="application/rss+xml" />
		<description><![CDATA[The most recent posts in script auto numerotation des bornes.]]></description>
		<lastBuildDate>Fri, 13 Mar 2026 23:11:38 +0000</lastBuildDate>
		<generator>PunBB</generator>
		<item>
			<title><![CDATA[Re: script auto numerotation des bornes]]></title>
			<link>https://qelectrotech.org/forum/viewtopic.php?pid=22692#p22692</link>
			<description><![CDATA[<p> <a href="https://drive.google.com/drive/folders/1gE0x8sLaZ0xIycHCxtgrd95E2zsUl-1L">https://drive.google.com/drive/folders/ … 5E2zsUl-1L</a></p><p>qet element.py </p>]]></description>
			<author><![CDATA[null@example.com (javdenech)]]></author>
			<pubDate>Fri, 13 Mar 2026 23:11:38 +0000</pubDate>
			<guid>https://qelectrotech.org/forum/viewtopic.php?pid=22692#p22692</guid>
		</item>
		<item>
			<title><![CDATA[Re: script auto numerotation des bornes]]></title>
			<link>https://qelectrotech.org/forum/viewtopic.php?pid=22590#p22590</link>
			<description><![CDATA[<p> this script searh all element deginning by Xxx: to make a list</p><p>X1<br />X2<br />X3<br />X4<br />X5</p><p>so you just have to put several X1 in your driagram</p><p>if you click on X1 in the list it will put X1:00 X1:01 with left to right logic across all the folio</p><p>you can also activate with wire to have X1:00_100 etc to concatenate with the num value of the wire connected on it</p><p>this for use with the new generator in dev actually</p><p>also if the you make modification it will renew all the name on a already taged X1:xxx element </p>]]></description>
			<author><![CDATA[null@example.com (javdenech)]]></author>
			<pubDate>Tue, 10 Feb 2026 11:38:47 +0000</pubDate>
			<guid>https://qelectrotech.org/forum/viewtopic.php?pid=22590#p22590</guid>
		</item>
		<item>
			<title><![CDATA[Re: script auto numerotation des bornes]]></title>
			<link>https://qelectrotech.org/forum/viewtopic.php?pid=22587#p22587</link>
			<description><![CDATA[<p> </p><div class="codebox"><pre><code>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Programme pour autonuméroter les bornes dans les fichiers QElectroTech (.qet)
Version finale qui fonctionne correctement
&quot;&quot;&quot;

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import xml.etree.ElementTree as ET
import os
from collections import OrderedDict

class QETBorneNumbering:
    def __init__(self, root):
        self.root = root
        self.root.title(&quot;Autonumérotation des bornes QElectroTech&quot;)
        self.root.geometry(&quot;800x600&quot;)
        
        # Variables
        self.file_path = None
        self.xml_root = None
        self.bornes_trouvees = []
        self.borne_selectionnee = None
        self.use_wire_num = tk.BooleanVar(value=False)
        
        self.create_widgets()
    
    def create_widgets(self):
        &quot;&quot;&quot;Crée l&#039;interface graphique&quot;&quot;&quot;
        # Frame principale
        main_frame = ttk.Frame(self.root, padding=&quot;10&quot;)
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # Configuration du poids pour le redimensionnement
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        main_frame.rowconfigure(3, weight=1)
        
        # Bouton pour ouvrir le fichier
        btn_open = ttk.Button(main_frame, text=&quot;Ouvrir un fichier .qet&quot;, command=self.open_file)
        btn_open.grid(row=0, column=0, columnspan=2, pady=10, sticky=tk.W+tk.E)
        
        # Label pour afficher le fichier ouvert
        self.label_file = ttk.Label(main_frame, text=&quot;Aucun fichier sélectionné&quot;, foreground=&quot;gray&quot;)
        self.label_file.grid(row=1, column=0, columnspan=2, sticky=tk.W)
        
        # Frame pour les bornes trouvées
        frame_bornes = ttk.LabelFrame(main_frame, text=&quot;Bornes trouvées&quot;, padding=&quot;5&quot;)
        frame_bornes.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=10)
        frame_bornes.columnconfigure(0, weight=1)
        
        # Listbox pour les bornes
        self.listbox_bornes = tk.Listbox(frame_bornes, height=6)
        self.listbox_bornes.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        self.listbox_bornes.bind(&#039;&lt;&lt;ListboxSelect&gt;&gt;&#039;, self.on_borne_select)
        
        # Scrollbar pour la listbox
        scrollbar = ttk.Scrollbar(frame_bornes, orient=&quot;vertical&quot;, command=self.listbox_bornes.yview)
        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        self.listbox_bornes.configure(yscrollcommand=scrollbar.set)
        
        # Frame pour les boutons d&#039;action
        frame_actions = ttk.LabelFrame(main_frame, text=&quot;Actions&quot;, padding=&quot;5&quot;)
        frame_actions.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10)
        frame_actions.columnconfigure(0, weight=1)
        
        # Bouton d&#039;autonumérotation
        self.btn_numeroter = ttk.Button(frame_actions, text=&quot;Numéroter les bornes sélectionnées&quot;, 
                                       command=self.numeroter_bornes, state=&quot;disabled&quot;)
        self.btn_numeroter.grid(row=0, column=0, pady=5, sticky=tk.W+tk.E)

        # Checkbox pour le mode de numérotation (Numéro de fil)
        self.chk_wire = ttk.Checkbutton(frame_actions, text=&quot;Utiliser n° fil (Label_Fil)&quot;, 
                                       variable=self.use_wire_num)
        self.chk_wire.grid(row=1, column=0, pady=5, sticky=tk.W)
        
        # Bouton pour sauvegarder
        self.btn_save = ttk.Button(frame_actions, text=&quot;Sauvegarder le fichier&quot;, 
                                  command=self.save_file, state=&quot;disabled&quot;)
        self.btn_save.grid(row=1, column=0, pady=5, sticky=tk.W+tk.E)
        self.btn_save.grid(row=2, column=0, pady=5, sticky=tk.W+tk.E)
        
        # Zone de statut
        self.status_var = tk.StringVar(value=&quot;Prêt&quot;)
        status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN)
        status_bar.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))
    
    def open_file(self):
        &quot;&quot;&quot;Ouvre un fichier .qet et analyse les bornes&quot;&quot;&quot;
        file_path = filedialog.askopenfilename(
            title=&quot;Sélectionner un fichier QElectroTech&quot;,
            filetypes=[(&quot;Fichiers QElectroTech&quot;, &quot;*.qet&quot;), (&quot;Tous les fichiers&quot;, &quot;*.*&quot;)]
        )
        
        if file_path:
            try:
                self.file_path = file_path
                self.label_file.config(text=f&quot;Fichier: {os.path.basename(file_path)}&quot;)
                
                # Lecture et analyse du fichier XML
                self.analyze_file()
                
                # Réinitialiser l&#039;état de modification pour le nouveau fichier
                if hasattr(self, &#039;_already_modified&#039;):
                    del self._already_modified
                
                self.status_var.set(f&quot;Fichier chargé: {len(self.bornes_trouvees)} bornes trouvées&quot;)
                
            except Exception as e:
                messagebox.showerror(&quot;Erreur&quot;, f&quot;Impossible d&#039;ouvrir le fichier:\n{str(e)}&quot;)
                self.status_var.set(&quot;Erreur lors de l&#039;ouverture du fichier&quot;)
    
    def analyze_file(self):
        &quot;&quot;&quot;Analyse le fichier XML pour trouver les bornes&quot;&quot;&quot;
        try:
            # Parser le fichier XML
            tree = ET.parse(self.file_path)
            self.xml_root = tree.getroot()
            
            # Extraire les bornes
            self.bornes_trouvees = self.extract_bornes()
            
            # Mettre à jour la listbox (sans doublons)
            self.update_listbox()
            
        except ET.ParseError as e:
            raise Exception(f&quot;Erreur de parsing XML: {str(e)}&quot;)
        except Exception as e:
            raise Exception(f&quot;Erreur lors de l&#039;analyse: {str(e)}&quot;)
    
    def extract_bornes(self):
        &quot;&quot;&quot;Extrait toutes les bornes du fichier XML&quot;&quot;&quot;
        bornes = []
        
        # Parcourir tous les diagrammes pour trouver les folios
        for diagram in self.xml_root.iter(&#039;diagram&#039;):
            folio = diagram.get(&#039;folio&#039;, &#039;1&#039;)
            
            # Parcourir les éléments dans ce diagramme
            for element in diagram.iter(&#039;element&#039;):
                x = float(element.get(&#039;x&#039;, &#039;0&#039;))
                y = float(element.get(&#039;y&#039;, &#039;0&#039;))
                
                # Vérifier si c&#039;est une borne (commence par X)
                for child in element:
                    if child.tag == &#039;elementInformations&#039;:
                        for info in child:
                            if info.tag == &#039;elementInformation&#039;:
                                name = info.get(&#039;name&#039;, &#039;&#039;)
                                if name == &#039;label&#039;:
                                    text = info.text
                                    if text and text.upper().startswith(&#039;X&#039;):
                                        bornes.append({
                                            &#039;element&#039;: element,
                                            &#039;label&#039;: text,
                                            &#039;position&#039;: (x, y),
                                            &#039;folio&#039;: folio
                                        })
                                        break
        
        return bornes
    
    def get_borne_base(self, label):
        &quot;&quot;&quot;Extrait la base du label (tronque après &#039;:&#039;)&quot;&quot;&quot;
        if &#039;:&#039; in label:
            return label.split(&#039;:&#039;)[0]
        return label
    
    def update_listbox(self):
        &quot;&quot;&quot;Met à jour la listbox avec les bornes uniques&quot;&quot;&quot;
        self.listbox_bornes.delete(0, tk.END)
        
        # Extraire les bases de labels uniques (tronquer avant &#039;:&#039;)
        bases_uniques = list(OrderedDict.fromkeys([self.get_borne_base(b[&#039;label&#039;]) for b in self.bornes_trouvees]))
        
        for label in sorted(bases_uniques):
            self.listbox_bornes.insert(tk.END, label)
        
        # Activer le bouton de numérotation si des bornes sont trouvées
        if bases_uniques:
            self.btn_numeroter.config(state=&quot;normal&quot;)
        else:
            self.btn_numeroter.config(state=&quot;disabled&quot;)
    
    def on_borne_select(self, event):
        &quot;&quot;&quot;Gère la sélection d&#039;une borne dans la listbox&quot;&quot;&quot;
        selection = self.listbox_bornes.curselection()
        if selection:
            index = selection[0]
            self.borne_selectionnee = self.listbox_bornes.get(index)
            self.status_var.set(f&quot;Borne sélectionnée: {self.borne_selectionnee}&quot;)
    
    def get_connected_wire_text(self, diagram, element_id):
        &quot;&quot;&quot;Trouve le texte du conducteur connecté à un élément donné&quot;&quot;&quot;
        if not element_id:
            return &quot;&quot;
        # Chercher dans les conducteurs du diagramme
        for conductor in diagram.iter(&#039;conductor&#039;):
            # Vérification directe des attributs element1 et element2 (Format QET standard)
            # Le numéro de fil est dans l&#039;attribut &#039;num&#039;
            if conductor.get(&#039;element1&#039;) == element_id or conductor.get(&#039;element2&#039;) == element_id:
                text = conductor.get(&#039;num&#039;, &#039;&#039;)
                if text and text.strip() and text.strip() != &quot;_&quot;:
                    return text.strip()
            
            # Compatibilité avec d&#039;autres formats potentiels (points de connexion)
            for point in conductor.iter(&#039;point&#039;):
                if point.get(&#039;element&#039;) == element_id:
                    # Vérifier l&#039;attribut text ou num
                    text = conductor.get(&#039;text&#039;, &#039;&#039;)
                    if not text:
                        text = conductor.get(&#039;num&#039;, &#039;&#039;)
                    
                    if text and text.strip() and text.strip() != &quot;_&quot;:
                        return text.strip()
        return &quot;&quot;

    def numeroter_bornes(self):
        &quot;&quot;&quot;Numérote les bornes sélectionnées dans leur ordre d&#039;apparition&quot;&quot;&quot;
        if not self.borne_selectionnee:
            messagebox.showwarning(&quot;Avertissement&quot;, &quot;Veuillez sélectionner une borne à numéroter&quot;)
            return
        
        try:
            # Si c&#039;est la première numérotation, recharger le fichier XML
            if not hasattr(self, &#039;_already_modified&#039;):
                tree = ET.parse(self.file_path)
                self.xml_root = tree.getroot()
                self._already_modified = True
            # Sinon, utiliser l&#039;arbre XML déjà modifié
            
            # Filtrer les bornes correspondant à la base sélectionnée
            bornes_a_numeroter = []
            for diagram in self.xml_root.iter(&#039;diagram&#039;):
                folio = diagram.get(&#039;folio&#039;, &#039;1&#039;)
                for element in diagram.iter(&#039;element&#039;):
                    x = float(element.get(&#039;x&#039;, &#039;0&#039;))
                    y = float(element.get(&#039;y&#039;, &#039;0&#039;))
                    
                    for child in element:
                        if child.tag == &#039;elementInformations&#039;:
                            for info in child:
                                if info.tag == &#039;elementInformation&#039;:
                                    name = info.get(&#039;name&#039;, &#039;&#039;)
                                    if name == &#039;label&#039;:
                                        text = info.text
                                        if text and self.get_borne_base(text) == self.borne_selectionnee:
                                            bornes_a_numeroter.append({
                                                &#039;element&#039;: element,
                                                &#039;label&#039;: text,
                                                &#039;position&#039;: (x, y),
                                                &#039;folio&#039;: folio,
                                                &#039;diagram&#039;: diagram # Nécessaire pour trouver les fils
                                            })
                                            break
            
            if not bornes_a_numeroter:
                messagebox.showwarning(&quot;Avertissement&quot;, &quot;Aucune borne trouvée pour ce label&quot;)
                return
            
            def natural_sort_key(s):
                &quot;&quot;&quot;Fonction utilitaire pour trier les folios numériquement si possible&quot;&quot;&quot;
                import re
                return [int(text) if text.isdigit() else text.lower()
                        for text in re.split(&#039;([0-9]+)&#039;, s)]

            # Trier par folio puis par position (gauche à droite)
            # On utilise natural_sort_key pour le folio pour éviter que &quot;10&quot; soit avant &quot;2&quot;
            bornes_triees = sorted(bornes_a_numeroter, 
                                 key=lambda x: (natural_sort_key(x[&#039;folio&#039;]), x[&#039;position&#039;][0], x[&#039;position&#039;][1]))
            
            # Numéroter les bornes avec format X1:00, X1:01, etc.
            for i, borne in enumerate(bornes_triees):
                num_seq = i + 1
                if self.use_wire_num.get():
                    # Mode concaténation : X1:1_102 (Bornier:Index_Fil)
                    # QET utilise l&#039;attribut &#039;uuid&#039; pour l&#039;identifiant unique
                    element_id = borne[&#039;element&#039;].get(&#039;uuid&#039;)
                    if not element_id:
                        element_id = borne[&#039;element&#039;].get(&#039;id&#039;)
                        
                    wire_text = self.get_connected_wire_text(borne[&#039;diagram&#039;], element_id)
                    suffixe = wire_text if wire_text else &quot;?&quot;
                    nouveau_label = f&quot;{self.borne_selectionnee}:{num_seq}_{suffixe}&quot;
                else:
                    # Mode séquentiel classique
                    nouveau_label = f&quot;{self.borne_selectionnee}:{num_seq}&quot;
                
                # Modifier elementInformation
                for child in borne[&#039;element&#039;]:
                    if child.tag == &#039;elementInformations&#039;:
                        for info in child:
                            if info.tag == &#039;elementInformation&#039;:
                                name = info.get(&#039;name&#039;, &#039;&#039;)
                                if name == &#039;label&#039;:
                                    info.text = nouveau_label
                    
                    # Modifier dynamic_text
                    elif child.tag == &#039;dynamic_texts&#039;:
                        for dynamic_text in child:
                            if dynamic_text.tag == &#039;dynamic_elmt_text&#039;:
                                for sub_child in dynamic_text:
                                    if sub_child.tag == &#039;info_name&#039; and sub_child.text == &#039;label&#039;:
                                        for text_elem in dynamic_text:
                                            if text_elem.tag == &#039;text&#039;:
                                                text_elem.text = nouveau_label
            
            self.status_var.set(f&quot;{len(bornes_triees)} bornes {self.borne_selectionnee} numérotées avec succès&quot;)
            self.btn_save.config(state=&quot;normal&quot;)
            
            messagebox.showinfo(&quot;Succès&quot;, f&quot;{len(bornes_triees)} bornes {self.borne_selectionnee} ont été numérotées&quot;)
            
        except Exception as e:
            messagebox.showerror(&quot;Erreur&quot;, f&quot;Erreur lors de la numérotation:\n{str(e)}&quot;)
    
    def save_file(self):
        &quot;&quot;&quot;Sauvegarde le fichier modifié&quot;&quot;&quot;
        if not self.xml_root:
            messagebox.showwarning(&quot;Avertissement&quot;, &quot;Aucun fichier à sauvegarder&quot;)
            return
        
        try:
            # Demander où sauvegarder
            save_path = filedialog.asksaveasfilename(
                title=&quot;Sauvegarder le fichier&quot;,
                defaultextension=&quot;.qet&quot;,
                filetypes=[(&quot;Fichiers QElectroTech&quot;, &quot;*.qet&quot;), (&quot;Tous les fichiers&quot;, &quot;*.*&quot;)],
                initialfile=os.path.basename(self.file_path)
            )
            
            if save_path:
                # Créer une copie de l&#039;arbre XML pour la sauvegarde
                tree = ET.ElementTree(self.xml_root)
                
                # Configuration pour un beau formatage
                ET.indent(tree, space=&quot;  &quot;, level=0)
                
                # Sauvegarder le fichier
                tree.write(save_path, encoding=&#039;utf-8&#039;, xml_declaration=True)
                
                self.status_var.set(f&quot;Fichier sauvegardé: {os.path.basename(save_path)}&quot;)
                messagebox.showinfo(&quot;Succès&quot;, &quot;Fichier sauvegardé avec succès&quot;)
                
        except Exception as e:
            messagebox.showerror(&quot;Erreur&quot;, f&quot;Erreur lors de la sauvegarde:\n{str(e)}&quot;)

def main():
    &quot;&quot;&quot;Fonction principale&quot;&quot;&quot;
    root = tk.Tk()
    app = QETBorneNumbering(root)
    root.mainloop()

if __name__ == &quot;__main__&quot;:
    main()</code></pre></div><p> </p>]]></description>
			<author><![CDATA[null@example.com (javdenech)]]></author>
			<pubDate>Mon, 09 Feb 2026 10:49:53 +0000</pubDate>
			<guid>https://qelectrotech.org/forum/viewtopic.php?pid=22587#p22587</guid>
		</item>
		<item>
			<title><![CDATA[script auto numerotation des bornes]]></title>
			<link>https://qelectrotech.org/forum/viewtopic.php?pid=22586#p22586</link>
			<description><![CDATA[<p> Script pour l&#039;auto numérotation des bornes </p>]]></description>
			<author><![CDATA[null@example.com (javdenech)]]></author>
			<pubDate>Mon, 09 Feb 2026 10:48:46 +0000</pubDate>
			<guid>https://qelectrotech.org/forum/viewtopic.php?pid=22586#p22586</guid>
		</item>
	</channel>
</rss>
