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...
You are not logged in. Please login or register.
QElectroTech → Scripts → 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...
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()
pip install pyinstaller
pyinstaller --noconsole --onefile QET_NOMENCLATURE.py
pour compiler en executable
very nice i was thinking about the same. thank u
I will translate it into german and share it with u
Please provide an little example project, pictures or video, if you can?
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.
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.
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 will upgrade to use tksheet later also.
QElectroTech → Scripts → script edition nomemclature
Powered by PunBB, supported by Informer Technologies, Inc.
Generated in 0.022 seconds (36% PHP - 64% DB) with 12 queries