1 (edited by javdenech 2026-02-07 14:09:06)

Topic: script edition nomemclature

Voici un script python que j'ai creer pour éditer les propriétés des éléments dans un schema afin de pouvoir saisir directement le fabricant article description commentaire etc...

Re: script edition nomemclature

import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
from tkinter import ttk
import xml.etree.ElementTree as ET
from pathlib import Path
import sys
import csv
from datetime import datetime

class QETAnalyzer:
    def __init__(self, root):
        self.root = root
        self.root.title("QElectroTech Nomenclature - Analyseur")
        self.root.geometry("1200x700")
        
        self.tree_file = None
        self.file_path = None
        self.elements_data = []
        self.columns_hidden = False  # État d'affichage des colonnes
        
        # Définir l'ordre des colonnes une seule fois
        self.columns_order = ('LABEL', 'FABRICANT', 'NUM ARTICLE', 'DESCRIPTION', 'COMMENTAIRE', 
                              'LOCALISATION', 'INSTALLATION', 'BLOC AUX 1', 'BLOC AUX 2', 'FONCTION',
                              'NUMERO DE COMMANDE', 'FOURNISSEUR', 'NUMERO INTERNE', 'UNITE', 'QUANTITE', 'FOLIO')
        
        # Style
        style = ttk.Style()
        style.theme_use('clam')
        
        # Frame principal
        main_frame = ttk.Frame(root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # Frame du haut avec boutons
        top_frame = ttk.Frame(main_frame)
        top_frame.pack(fill=tk.X, pady=5)
        
        ttk.Button(top_frame, text="Ouvrir fichier .qet", command=self.open_file).pack(side=tk.LEFT, padx=5)
        ttk.Button(top_frame, text="Sauvegarder", command=self.save_file).pack(side=tk.LEFT, padx=5)
        ttk.Button(top_frame, text="Exporter en CSV", command=self.export_csv).pack(side=tk.LEFT, padx=5)
        ttk.Button(top_frame, text="Masquer/Afficher colonnes", command=self.toggle_columns).pack(side=tk.LEFT, padx=5)
        
        self.file_label = ttk.Label(top_frame, text="Aucun fichier ouvert", foreground="gray")
        self.file_label.pack(side=tk.LEFT, padx=20)
        
        # Frame pour le tableau
        tree_frame = ttk.Frame(main_frame)
        tree_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        
        # Scrollbars
        vsb = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL)
        hsb = ttk.Scrollbar(tree_frame, orient=tk.HORIZONTAL)
        
        # Treeview
        columns = self.columns_order
        self.tree = ttk.Treeview(
            tree_frame,
            columns=columns,
            show='headings',
            yscrollcommand=vsb.set,
            xscrollcommand=hsb.set,
            height=25
        )
        
        vsb.config(command=self.tree.yview)
        hsb.config(command=self.tree.xview)
        
        # Définir les colonnes
        widths = [80, 70, 80, 100, 120, 100, 100, 100, 100, 120, 80, 100, 70, 70, 80]
        for col, width in zip(columns, widths):
            self.tree.column(col, width=width, anchor=tk.W)
            self.tree.heading(col, text=col, command=lambda c=col: self.sort_column(c, False))
        
        # Bind double-click pour éditer
        self.tree.bind('<Double-1>', self.on_tree_double_click)
        
        self.tree.grid(row=0, column=0, sticky='nsew')
        vsb.grid(row=0, column=1, sticky='ns')
        hsb.grid(row=1, column=0, sticky='ew')
        
        tree_frame.grid_rowconfigure(0, weight=1)
        tree_frame.grid_columnconfigure(0, weight=1)
        
        # Frame du bas avec info
        bottom_frame = ttk.Frame(main_frame)
        bottom_frame.pack(fill=tk.X, pady=5)
        
        self.info_label = ttk.Label(bottom_frame, text="Éléments: 0")
        self.info_label.pack(side=tk.LEFT)
    
    def open_file(self):
        file_path = filedialog.askopenfilename(
            title="Sélectionner un fichier QElectroTech",
            filetypes=[("QET files", "*.qet"), ("All files", "*.*")]
        )
        
        if not file_path:
            return
        
        try:
            self.file_path = file_path
            self.tree_file = ET.parse(file_path)
            self.load_elements()
            self.file_label.config(text=f"Fichier: {Path(file_path).name}", foreground="green")
        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de l'ouverture du fichier:\n{str(e)}")
    
    def load_elements(self):
        """Charger les éléments du fichier QET"""
        self.tree.delete(*self.tree.get_children())
        self.elements_data = []
        
        if not self.tree_file:
            return
        
        root = self.tree_file.getroot()
        
        # Chercher tous les éléments dans le diagramme
        for diagram in root.findall('.//diagram'):
            # Extraire le numéro de folio depuis l'attribut folio et le titre du diagramme
            folio_title = diagram.get('title', '')
            folio_attr = diagram.get('folio', '')
            folio_number = self.extract_folio_number(folio_title, folio_attr)
            
            for element in diagram.findall('.//element'):
                # Ne garder que les éléments qui ont des elementInformations
                if element.find('.//elementInformations') is None:
                    continue
                
                # Filtrer les report de folio (coming_arrow et going_arrow)
                element_type = element.get('type', '')
                if '01coming_arrow.elmt' in element_type or '02going_arrow.elmt' in element_type:
                    continue
                
                # Filtrer les éléments avec une seule terminaison
                terminals = element.findall('.//terminal')
                if len(terminals) <= 1:
                    # Vérifier si le label commence par 'W' (câbles)
                    element_info = self.extract_element_info(element)
                    if not element_info['label'] or not element_info['label'].strip().upper().startswith('W'):
                        continue
                
                element_info = self.extract_element_info(element)
                
                # Ne garder que les éléments avec un label contenant au moins une lettre (A-Z)
                label = element_info['label'].strip()
                if not label or not any(c.isalpha() for c in label):
                    continue
                
                self.elements_data.append({
                    'element': element,
                    'uuid': element_info['uuid'],
                    'label': element_info['label'],
                    'quantity': element_info['quantity'],
                    'function': element_info['function'],
                    'designation': element_info['designation'],
                    'description': element_info['description'],
                    'comment': element_info['comment'],
                    'location': element_info['location'],
                    'manufacturer': element_info['manufacturer'],
                    'supplier': element_info['supplier'],
                    'manufacturer_reference': element_info['manufacturer_reference'],
                    'plant': element_info['plant'],
                    'machine_manufacturer_reference': element_info['machine_manufacturer_reference'],
                    'auxiliary1': element_info['auxiliary1'],
                    'auxiliary2': element_info['auxiliary2'],
                    'unity': element_info['unity'],
                    'folio': folio_number
                })
        
        # Tri par défaut sur le label
        self.elements_data.sort(key=lambda x: x['label'])
        
        self.populate_tree()                

        
        self.info_label.config(text=f"Éléments affichés: {len(self.elements_data)}")
        
    def populate_tree(self):
        """Remplir le tableau avec les données actuelles"""
        self.tree.delete(*self.tree.get_children())
        for item in self.elements_data:
            self.tree.insert('', 'end', values=(
                item.get('label', ''),
                item.get('manufacturer', ''),
                item.get('designation', ''),
                item.get('description', ''),
                item.get('comment', ''),
                item.get('location', ''),
                item.get('plant', ''),
                item.get('auxiliary1', ''),
                item.get('auxiliary2', ''),
                item.get('function', ''),
                item.get('manufacturer_reference', ''),
                item.get('supplier', ''),
                item.get('machine_manufacturer_reference', ''),
                item.get('unity', ''),
                item.get('quantity', ''),
                item.get('folio', '')
            ))
        
        # Redimensionner automatiquement les colonnes
        self.auto_resize_columns()

    def sort_column(self, col, reverse):
        """Trier le tableau selon une colonne"""
        col_map = {
            'LABEL': 'label', 'FABRICANT': 'manufacturer', 'NUM ARTICLE': 'designation',
            'DESCRIPTION': 'description', 'COMMENTAIRE': 'comment', 'LOCALISATION': 'location',
            'INSTALLATION': 'plant', 'BLOC AUX 1': 'auxiliary1', 'BLOC AUX 2': 'auxiliary2',
            'FONCTION': 'function', 'NUMERO DE COMMANDE': 'manufacturer_reference',
            'FOURNISSEUR': 'supplier', 'NUMERO INTERNE': 'machine_manufacturer_reference',
            'UNITE': 'unity', 'QUANTITE': 'quantity', 'FOLIO': 'folio'
        }
        
        key = col_map.get(col, 'label')
        
        # Trier les données (case insensitive)
        self.elements_data.sort(key=lambda x: x[key].lower() if x[key] else '', reverse=reverse)
        
        # Mettre à jour l'affichage
        self.populate_tree()
        
        # Mettre à jour l'en-tête pour inverser le tri au prochain clic
        self.tree.heading(col, command=lambda: self.sort_column(col, not reverse))
    
    def extract_element_info(self, element):
        """Extraire les informations d'un élément"""
        uuid = element.get('uuid', 'N/A')
        element_type = element.get('type', 'N/A')
        prefix = element.get('prefix', '')
        
        # Initialiser tous les champs
        label = ''
        quantity = ''
        function = ''
        designation = ''
        description = ''
        comment = ''
        location = ''
        manufacturer = ''
        supplier = ''
        manufacturer_reference = ''
        plant = ''
        machine_manufacturer_reference = ''
        auxiliary1 = ''
        auxiliary2 = ''
        unity = ''
        
        elem_infos = element.find('.//elementInformations')
        if elem_infos is not None:
            for info in elem_infos.findall('elementInformation'):
                name = info.get('name', '')
                text = info.text or ''
                
                if name == 'label':
                    label = text
                elif name == 'quantity':
                    quantity = text
                elif name == 'function':
                    function = text
                elif name == 'designation':
                    designation = text
                elif name == 'description':
                    description = text
                elif name == 'comment':
                    comment = text
                elif name == 'location':
                    location = text
                elif name == 'manufacturer':
                    manufacturer = text
                elif name == 'supplier':
                    supplier = text
                elif name == 'manufacturer_reference':
                    manufacturer_reference = text
                elif name == 'plant':
                    plant = text
                elif name == 'machine_manufacturer_reference':
                    machine_manufacturer_reference = text
                elif name == 'auxiliary1':
                    auxiliary1 = text
                elif name == 'auxiliary2':
                    auxiliary2 = text
                elif name == 'unity':
                    unity = text
        
        # Sinon chercher dans dynamic_texts pour le label
        if not label:
            dynamic_texts = element.find('.//dynamic_texts')
            if dynamic_texts is not None:
                for text_elem in dynamic_texts.findall('.//dynamic_text'):
                    # Vérifier si c'est le label
                    info_name = text_elem.find('info_name')
                    if info_name is not None and info_name.text == 'label':
                        text_content = text_elem.find('text')
                        if text_content is not None and text_content.text:
                            label = text_content.text
                            break
        
        return {
            'uuid': uuid,
            'type': element_type.split('/')[-1] if '/' in element_type else element_type,
            'prefix': prefix,
            'label': label,
            'quantity': quantity,
            'function': function,
            'designation': designation,
            'description': description,
            'comment': comment,
            'location': location,
            'manufacturer': manufacturer,
            'supplier': supplier,
            'manufacturer_reference': manufacturer_reference,
            'plant': plant,
            'machine_manufacturer_reference': machine_manufacturer_reference,
            'auxiliary1': auxiliary1,
            'auxiliary2': auxiliary2,
            'unity': unity
        }
    
    def extract_folio_number(self, folio_title, folio_attr):
        """Extraire le numéro de folio depuis l'attribut folio ou le titre du diagramme"""
        # Priorité à l'attribut folio qui contient toujours le numéro
        if folio_attr and folio_attr.strip():
            return folio_attr.strip()
        
        # Sinon chercher un numéro dans le titre (fallback)
        if not folio_title:
            return ''
        
        # Chercher un numéro dans le titre (ex: "Folio 01", "Page 1", etc.)
        import re
        match = re.search(r'(\d+)', folio_title)
        if match:
            return match.group(1)
        
        # Si pas de numéro trouvé, retourner le titre tel quel
        return folio_title
    
    def on_tree_double_click(self, event):
        """Gérer le double-clic pour éditer directement dans la cellule"""
        region = self.tree.identify_region(event.x, event.y)
        if region == "cell":
            column = self.tree.identify_column(event.x)
            item = self.tree.identify_row(event.y)
            
            if item and column:
                # Récupérer l'index de la colonne
                col_index = int(column[1:]) - 1
                col_name = self.columns_order[col_index]
                
                # Interdire l'édition de la colonne FOLIO
                if col_name == 'FOLIO':
                    return
                
                # Interdire l'édition de la colonne LABEL
                if col_name == 'LABEL':
                    return
                
                # Récupérer la valeur actuelle
                current_value = self.tree.set(item, column)
                
                # Créer un widget Entry pour l'édition
                self.edit_item = item
                self.edit_col_index = col_index
                self.edit_col_name = col_name
                
                # Récupérer les données de l'élément
                item_values = self.tree.item(item)['values']
                item_label = item_values[0]  # Le label est maintenant la première colonne
                
                for elem_data in self.elements_data:
                    if elem_data['label'] == item_label:
                        self.edit_element_data = elem_data
                        break
                
                # Créer et positionner le widget Entry
                self.edit_entry = ttk.Entry(self.tree)
                self.edit_entry.insert(0, current_value)
                self.edit_entry.place(x=event.x, y=event.y, width=100)
                
                # Lier les événements
                self.edit_entry.bind('<Return>', self.save_edit)
                self.edit_entry.bind('<Escape>', self.cancel_edit)
                self.edit_entry.bind('<FocusOut>', self.save_edit)
                
                self.edit_entry.focus()
                self.edit_entry.select_range(0, 'end')
    
    def save_edit(self, event=None):
        """Sauvegarder l'édition"""
        if not hasattr(self, 'edit_entry') or not self.edit_entry.winfo_exists():
            return
        
        new_value = self.edit_entry.get()
        self.edit_entry.destroy()
        
        # Map pour convertir les noms de colonnes en noms de champs XML
        field_map = {
            'LABEL': 'label',
            'QUANTITE': 'quantity',
            'FONCTION': 'function',
            'NUM ARTICLE': 'designation',
            'DESCRIPTION': 'description',
            'COMMENTAIRE': 'comment',
            'LOCALISATION': 'location',
            'FABRICANT': 'manufacturer',
            'FOURNISSEUR': 'supplier',
            'NUMERO DE COMMANDE': 'manufacturer_reference',
            'INSTALLATION': 'plant',
            'NUMERO INTERNE': 'machine_manufacturer_reference',
            'BLOC AUX 1': 'auxiliary1',
            'BLOC AUX 2': 'auxiliary2',
            'UNITE': 'unity',
            'FOLIO': 'folio'
        }
        
        field_key = field_map.get(self.edit_col_name, self.edit_col_name.lower())
        
        # Mettre à jour le XML
        self.update_element_info(self.edit_element_data, field_key, new_value)
        
        # Mettre à jour le tableau
        row_values = list(self.tree.item(self.edit_item)['values'])
        row_values[self.edit_col_index] = new_value
        self.tree.item(self.edit_item, values=row_values)
        
        # Mettre à jour les données en mémoire
        self.edit_element_data[field_key] = new_value
    
    def cancel_edit(self, event=None):
        """Annuler l'édition"""
        if hasattr(self, 'edit_entry') and self.edit_entry.winfo_exists():
            self.edit_entry.destroy()
    
    def update_element_info(self, element_data, field_name, value):
        """Mettre à jour les informations de l'élément dans le XML"""
        element = element_data['element']
        field_key = field_name.lower()
        
        # Map inverse pour convertir les noms lisibles en noms XML
        field_xml_map = {
            'manufacturer ref': 'manufacturer_reference',
            'internal ref': 'machine_manufacturer_reference',
            'aux 1': 'auxiliary1',
            'aux 2': 'auxiliary2'
        }
        
        # Utiliser le nom XML correct
        xml_field_name = field_xml_map.get(field_key, field_key)
        
        if field_key == 'label':
            # Mettre à jour dans elementInformations
            elem_infos = element.find('.//elementInformations')
            if elem_infos is None:
                elem_infos = ET.SubElement(element, 'elementInformations')
            
            label_info = elem_infos.find("elementInformation[@name='label']")
            if label_info is None:
                label_info = ET.SubElement(elem_infos, 'elementInformation')
                label_info.set('name', 'label')
                label_info.set('show', '1')
            label_info.text = value
            
            # Aussi dans dynamic_texts
            dynamic_texts = element.find('.//dynamic_texts')
            if dynamic_texts is None:
                dynamic_texts = ET.SubElement(element, 'dynamic_texts')
            
            for text_elem in dynamic_texts.findall('.//dynamic_elmt_text'):
                text_content = text_elem.find('text')
                if text_content is not None:
                    text_content.text = value
                    break
        
        else:
            # Mettre à jour dans elementInformations
            elem_infos = element.find('.//elementInformations')
            if elem_infos is None:
                elem_infos = ET.SubElement(element, 'elementInformations')
            
            info = elem_infos.find(f"elementInformation[@name='{xml_field_name}']")
            if info is None:
                info = ET.SubElement(elem_infos, 'elementInformation')
                info.set('name', xml_field_name)
                info.set('show', '1')
            info.text = value
    
    def export_csv(self):
        """Exporter les éléments en CSV"""
        if not self.elements_data:
            messagebox.showwarning("Attention", "Aucun élément à exporter")
            return
        
        file_path = filedialog.asksaveasfilename(
            title="Exporter en CSV",
            defaultextension=".csv",
            filetypes=[("CSV files", "*.csv"), ("All files", "*.*")]
        )
        
        if not file_path:
            return
        
        try:
            columns = ('LABEL', 'FABRICANT', 'NUM ARTICLE', 'DESCRIPTION', 'COMMENTAIRE', 
                       'LOCALISATION', 'INSTALLATION', 'BLOC AUX 1', 'BLOC AUX 2', 'FONCTION',
                       'NUMERO DE COMMANDE', 'FOURNISSEUR', 'NUMERO INTERNE', 'UNITE', 'QUANTITE', 'FOLIO')
            
            with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
                writer = csv.writer(csvfile, delimiter=';')
                writer.writerow(columns)
                
                for element in self.elements_data:
                    row = [
                        element['label'],
                        element['manufacturer'],
                        element['designation'],
                        element['description'],
                        element['comment'],
                        element['location'],
                        element['plant'],
                        element['auxiliary1'],
                        element['auxiliary2'],
                        element['function'],
                        element['manufacturer_reference'],
                        element['supplier'],
                        element['machine_manufacturer_reference'],
                        element['unity'],
                        element['quantity'],
                        element['folio']
                    ]
                    writer.writerow(row)
            
            messagebox.showinfo("Succès", f"Fichier CSV créé: {Path(file_path).name}")
        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de l'export:\n{str(e)}")
    
    def add_element(self):
        """Ajouter un nouvel élément"""
        if not self.tree_file:
            messagebox.showwarning("Attention", "Veuillez d'abord ouvrir un fichier")
            return
        
        messagebox.showinfo("Non implémenté", "Ajout d'éléments non implémenté dans cette version")
    
    def delete_element(self):
        """Supprimer l'élément sélectionné"""
        selection = self.tree.selection()
        if not selection:
            messagebox.showwarning("Attention", "Sélectionnez un élément")
            return
        
        if messagebox.askyesno("Confirmation", "Êtes-vous sûr de vouloir supprimer cet élément?"):
            item = selection[0]
            row_values = self.tree.item(item)['values']
            element_uuid = row_values[0]
            
            # Trouver et supprimer l'élément du XML
            root = self.tree_file.getroot()
            for diagram in root.findall('.//diagram'):
                for element in diagram.findall('.//element'):
                    if element.get('uuid') == element_uuid:
                        diagram.remove(element)
                        break
            
            # Retirer de la liste
            self.elements_data = [e for e in self.elements_data if e['uuid'] != element_uuid]
            self.tree.delete(item)
            self.info_label.config(text=f"Éléments: {len(self.elements_data)}")
            messagebox.showinfo("Succès", "Élément supprimé")
    
    def save_file(self):
        """Sauvegarder les modifications"""
        if not self.file_path or not self.tree_file:
            messagebox.showwarning("Attention", "Aucun fichier ouvert")
            return
        
        try:
            # Sauvegarder avec indentation
            self.tree_file.write(self.file_path, encoding='utf-8', xml_declaration=True)
            messagebox.showinfo("Succès", f"Fichier sauvegardé: {Path(self.file_path).name}")
        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la sauvegarde:\n{str(e)}")
    
    def toggle_columns(self):
        """Masquer/Afficher les colonnes optionnelles et redimensionner automatiquement"""
        if not self.elements_data:
            messagebox.showwarning("Attention", "Aucun fichier chargé")
            return
        
        # Colonnes à masquer/afficher
        columns_to_toggle = ['BLOC AUX 1', 'BLOC AUX 2', 'NUMERO DE COMMANDE', 'INSTALLATION', 
                           'NUMERO INTERNE', 'UNITE', 'QUANTITE']
        
        if self.columns_hidden:
            # Afficher toutes les colonnes
            self.tree['displaycolumns'] = self.columns_order
            self.columns_hidden = False
        else:
            # Masquer les colonnes optionnelles
            visible_columns = [col for col in self.columns_order if col not in columns_to_toggle]
            self.tree['displaycolumns'] = visible_columns
            self.columns_hidden = True
        
        # Redimensionner automatiquement les colonnes visibles
        self.auto_resize_columns()
    
    def auto_resize_columns(self):
        """Redimensionner automatiquement les colonnes en fonction de leur contenu"""
        # Obtenir les colonnes actuellement affichées
        try:
            displayed_columns = list(self.tree['displaycolumns'])
        except:
            displayed_columns = list(self.columns_order)
        
        # Colonnes qui restent toujours visibles (si elles sont affichées)
        always_visible = ['LABEL', 'FABRICANT', 'NUM ARTICLE', 'DESCRIPTION', 'COMMENTAIRE', 
                         'LOCALISATION', 'POLES', 'FONCTION', 'FOURNISSEUR', 'FOLIO']
        
        # Largeur minimale pour chaque colonne
        min_widths = {
            'LABEL': 60,
            'FABRICANT': 80, 
            'NUM ARTICLE': 80,
            'DESCRIPTION': 100,
            'COMMENTAIRE': 100,
            'LOCALISATION': 80,
            'POLES': 90,
            'FONCTION': 80,
            'FOURNISSEUR': 80,
            'FOLIO': 50
        }
        
        # Calculer la largeur maximale nécessaire pour chaque colonne visible
        for col in displayed_columns:
            if col in self.columns_order:
                max_width = min_widths.get(col, 50)
                
                # Mesurer la largeur de l'en-tête
                header_width = len(col) * 8  # Approximation: 8 pixels par caractère
                max_width = max(max_width, header_width)
                
                # Mesurer la largeur du contenu de chaque ligne
                for child in self.tree.get_children():
                    try:
                        col_index = self.columns_order.index(col)
                        value = self.tree.item(child)['values'][col_index]
                        if value:
                            text_width = len(str(value)) * 8  # Approximation
                            max_width = max(max_width, text_width)
                    except (IndexError, ValueError):
                        pass
                
                # Appliquer la largeur avec une limite maximale
                final_width = min(max_width + 20, 300)  # +20 pour padding, max 300px
                self.tree.column(col, width=final_width, anchor=tk.W)
        
        # Forcer le rafraîchissement de l'affichage
        self.tree.update_idletasks()


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

Re: script edition nomemclature

pip install pyinstaller
pyinstaller --noconsole --onefile QET_NOMENCLATURE.py
pour compiler en executable

4 (edited by Kellermorph 2026-02-07 16:40:28)

Re: script edition nomemclature

very nice i was thinking about the same. thank u
I will translate it into german and share it with u

Re: script edition nomemclature

Please provide an little example project, pictures or video, if you can?

"Le jour où tu découvres le Libre, tu sais que tu ne pourras jamais plus revenir en arrière..."Questions regarding QET belong in this forum and will NOT be answered via PM! – Les questions concernant QET doivent être posées sur ce forum et ne seront pas traitées par MP !

Re: script edition nomemclature

Kellermorph wrote:

very nice i was thinking about the same. thank u
I will translate it into german and share it with u

English translation can help too.

"Le jour où tu découvres le Libre, tu sais que tu ne pourras jamais plus revenir en arrière..."Questions regarding QET belong in this forum and will NOT be answered via PM! – Les questions concernant QET doivent être posées sur ce forum et ne seront pas traitées par MP !

Re: script edition nomemclature

I tried your script.
However, it didn't work to write information directly to the QET file.
Unfortunately, I find your version very confusing.
I have now taken my PLC manager and modified it functionally so that it has the same function.
This gives you a page-by-page breakdown of the components, and I have added the missing variables.
My German version is attached.

Post's attachments

Attachment icon Eigenschaften.py 16.78 kb, 7 downloads since 2026-02-09 

Re: script edition nomemclature

it's running for me to write in the qet files the propertie of each element.
Don't forget to save the project and open the saving project.

it's the first release for the moment and i used if to french diagram may be should be adapted to used with other language, in case of i think of properties don"t have the same name, i need to check later,
i weel upgrade to use tksheet later also.