script updated tu use tksheet to copy paste from excel file
27 2026-02-10 13:38:47
Re: script auto numerotation des bornes (3 replies, posted in Scripts)
this script searh all element deginning by Xxx: to make a list
X1
X2
X3
X4
X5
so you just have to put several X1 in your driagram
if you click on X1 in the list it will put X1:00 X1:01 with left to right logic across all the folio
you can also activate with wire to have X1:00_100 etc to concatenate with the num value of the wire connected on it
this for use with the new generator in dev actually
also if the you make modification it will renew all the name on a already taged X1:xxx element
28 2026-02-10 13:31:19
Re: script edition nomemclature (10 replies, posted in Scripts)
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.
29 2026-02-09 12:49:53
Re: script auto numerotation des bornes (3 replies, posted in Scripts)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Programme pour autonuméroter les bornes dans les fichiers QElectroTech (.qet)
Version finale qui fonctionne correctement
"""
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("Autonumérotation des bornes QElectroTech")
self.root.geometry("800x600")
# 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):
"""Crée l'interface graphique"""
# Frame principale
main_frame = ttk.Frame(self.root, padding="10")
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="Ouvrir un fichier .qet", 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="Aucun fichier sélectionné", foreground="gray")
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="Bornes trouvées", padding="5")
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('<<ListboxSelect>>', self.on_borne_select)
# Scrollbar pour la listbox
scrollbar = ttk.Scrollbar(frame_bornes, orient="vertical", 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'action
frame_actions = ttk.LabelFrame(main_frame, text="Actions", padding="5")
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'autonumérotation
self.btn_numeroter = ttk.Button(frame_actions, text="Numéroter les bornes sélectionnées",
command=self.numeroter_bornes, state="disabled")
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="Utiliser n° fil (Label_Fil)",
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="Sauvegarder le fichier",
command=self.save_file, state="disabled")
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="Prêt")
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):
"""Ouvre un fichier .qet et analyse les bornes"""
file_path = filedialog.askopenfilename(
title="Sélectionner un fichier QElectroTech",
filetypes=[("Fichiers QElectroTech", "*.qet"), ("Tous les fichiers", "*.*")]
)
if file_path:
try:
self.file_path = file_path
self.label_file.config(text=f"Fichier: {os.path.basename(file_path)}")
# Lecture et analyse du fichier XML
self.analyze_file()
# Réinitialiser l'état de modification pour le nouveau fichier
if hasattr(self, '_already_modified'):
del self._already_modified
self.status_var.set(f"Fichier chargé: {len(self.bornes_trouvees)} bornes trouvées")
except Exception as e:
messagebox.showerror("Erreur", f"Impossible d'ouvrir le fichier:\n{str(e)}")
self.status_var.set("Erreur lors de l'ouverture du fichier")
def analyze_file(self):
"""Analyse le fichier XML pour trouver les bornes"""
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"Erreur de parsing XML: {str(e)}")
except Exception as e:
raise Exception(f"Erreur lors de l'analyse: {str(e)}")
def extract_bornes(self):
"""Extrait toutes les bornes du fichier XML"""
bornes = []
# Parcourir tous les diagrammes pour trouver les folios
for diagram in self.xml_root.iter('diagram'):
folio = diagram.get('folio', '1')
# Parcourir les éléments dans ce diagramme
for element in diagram.iter('element'):
x = float(element.get('x', '0'))
y = float(element.get('y', '0'))
# Vérifier si c'est une borne (commence par X)
for child in element:
if child.tag == 'elementInformations':
for info in child:
if info.tag == 'elementInformation':
name = info.get('name', '')
if name == 'label':
text = info.text
if text and text.upper().startswith('X'):
bornes.append({
'element': element,
'label': text,
'position': (x, y),
'folio': folio
})
break
return bornes
def get_borne_base(self, label):
"""Extrait la base du label (tronque après ':')"""
if ':' in label:
return label.split(':')[0]
return label
def update_listbox(self):
"""Met à jour la listbox avec les bornes uniques"""
self.listbox_bornes.delete(0, tk.END)
# Extraire les bases de labels uniques (tronquer avant ':')
bases_uniques = list(OrderedDict.fromkeys([self.get_borne_base(b['label']) 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="normal")
else:
self.btn_numeroter.config(state="disabled")
def on_borne_select(self, event):
"""Gère la sélection d'une borne dans la listbox"""
selection = self.listbox_bornes.curselection()
if selection:
index = selection[0]
self.borne_selectionnee = self.listbox_bornes.get(index)
self.status_var.set(f"Borne sélectionnée: {self.borne_selectionnee}")
def get_connected_wire_text(self, diagram, element_id):
"""Trouve le texte du conducteur connecté à un élément donné"""
if not element_id:
return ""
# Chercher dans les conducteurs du diagramme
for conductor in diagram.iter('conductor'):
# Vérification directe des attributs element1 et element2 (Format QET standard)
# Le numéro de fil est dans l'attribut 'num'
if conductor.get('element1') == element_id or conductor.get('element2') == element_id:
text = conductor.get('num', '')
if text and text.strip() and text.strip() != "_":
return text.strip()
# Compatibilité avec d'autres formats potentiels (points de connexion)
for point in conductor.iter('point'):
if point.get('element') == element_id:
# Vérifier l'attribut text ou num
text = conductor.get('text', '')
if not text:
text = conductor.get('num', '')
if text and text.strip() and text.strip() != "_":
return text.strip()
return ""
def numeroter_bornes(self):
"""Numérote les bornes sélectionnées dans leur ordre d'apparition"""
if not self.borne_selectionnee:
messagebox.showwarning("Avertissement", "Veuillez sélectionner une borne à numéroter")
return
try:
# Si c'est la première numérotation, recharger le fichier XML
if not hasattr(self, '_already_modified'):
tree = ET.parse(self.file_path)
self.xml_root = tree.getroot()
self._already_modified = True
# Sinon, utiliser l'arbre XML déjà modifié
# Filtrer les bornes correspondant à la base sélectionnée
bornes_a_numeroter = []
for diagram in self.xml_root.iter('diagram'):
folio = diagram.get('folio', '1')
for element in diagram.iter('element'):
x = float(element.get('x', '0'))
y = float(element.get('y', '0'))
for child in element:
if child.tag == 'elementInformations':
for info in child:
if info.tag == 'elementInformation':
name = info.get('name', '')
if name == 'label':
text = info.text
if text and self.get_borne_base(text) == self.borne_selectionnee:
bornes_a_numeroter.append({
'element': element,
'label': text,
'position': (x, y),
'folio': folio,
'diagram': diagram # Nécessaire pour trouver les fils
})
break
if not bornes_a_numeroter:
messagebox.showwarning("Avertissement", "Aucune borne trouvée pour ce label")
return
def natural_sort_key(s):
"""Fonction utilitaire pour trier les folios numériquement si possible"""
import re
return [int(text) if text.isdigit() else text.lower()
for text in re.split('([0-9]+)', s)]
# Trier par folio puis par position (gauche à droite)
# On utilise natural_sort_key pour le folio pour éviter que "10" soit avant "2"
bornes_triees = sorted(bornes_a_numeroter,
key=lambda x: (natural_sort_key(x['folio']), x['position'][0], x['position'][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'attribut 'uuid' pour l'identifiant unique
element_id = borne['element'].get('uuid')
if not element_id:
element_id = borne['element'].get('id')
wire_text = self.get_connected_wire_text(borne['diagram'], element_id)
suffixe = wire_text if wire_text else "?"
nouveau_label = f"{self.borne_selectionnee}:{num_seq}_{suffixe}"
else:
# Mode séquentiel classique
nouveau_label = f"{self.borne_selectionnee}:{num_seq}"
# Modifier elementInformation
for child in borne['element']:
if child.tag == 'elementInformations':
for info in child:
if info.tag == 'elementInformation':
name = info.get('name', '')
if name == 'label':
info.text = nouveau_label
# Modifier dynamic_text
elif child.tag == 'dynamic_texts':
for dynamic_text in child:
if dynamic_text.tag == 'dynamic_elmt_text':
for sub_child in dynamic_text:
if sub_child.tag == 'info_name' and sub_child.text == 'label':
for text_elem in dynamic_text:
if text_elem.tag == 'text':
text_elem.text = nouveau_label
self.status_var.set(f"{len(bornes_triees)} bornes {self.borne_selectionnee} numérotées avec succès")
self.btn_save.config(state="normal")
messagebox.showinfo("Succès", f"{len(bornes_triees)} bornes {self.borne_selectionnee} ont été numérotées")
except Exception as e:
messagebox.showerror("Erreur", f"Erreur lors de la numérotation:\n{str(e)}")
def save_file(self):
"""Sauvegarde le fichier modifié"""
if not self.xml_root:
messagebox.showwarning("Avertissement", "Aucun fichier à sauvegarder")
return
try:
# Demander où sauvegarder
save_path = filedialog.asksaveasfilename(
title="Sauvegarder le fichier",
defaultextension=".qet",
filetypes=[("Fichiers QElectroTech", "*.qet"), ("Tous les fichiers", "*.*")],
initialfile=os.path.basename(self.file_path)
)
if save_path:
# Créer une copie de l'arbre XML pour la sauvegarde
tree = ET.ElementTree(self.xml_root)
# Configuration pour un beau formatage
ET.indent(tree, space=" ", level=0)
# Sauvegarder le fichier
tree.write(save_path, encoding='utf-8', xml_declaration=True)
self.status_var.set(f"Fichier sauvegardé: {os.path.basename(save_path)}")
messagebox.showinfo("Succès", "Fichier sauvegardé avec succès")
except Exception as e:
messagebox.showerror("Erreur", f"Erreur lors de la sauvegarde:\n{str(e)}")
def main():
"""Fonction principale"""
root = tk.Tk()
app = QETBorneNumbering(root)
root.mainloop()
if __name__ == "__main__":
main()
30 2026-02-09 12:48:46
Topic: script auto numerotation des bornes (3 replies, posted in Scripts)
Script pour l'auto numérotation des bornes
31 2026-02-09 00:04:56
Re: script auto numérotation des fils par folio (6 replies, posted in Scripts)
Script mis a jour 08/02/2026
32 2026-02-08 23:21:58
Re: script auto numérotation des fils par folio (6 replies, posted in Scripts)
Hello scorpio; je fais une video dès que possible
33 2026-02-07 14:19:57
Re: script edition nomemclature (10 replies, posted in Scripts)
pip install pyinstaller
pyinstaller --noconsole --onefile QET_NOMENCLATURE.py
pour compiler en executable
34 2026-02-07 14:18:08
Re: script auto numérotation des fils par folio (6 replies, posted in Scripts)
pip install pyinstaller
pyinstaller --noconsole --onefile NUM_WIRE_QET.py
pour compiler en executable....
35 2026-02-07 14:06:34
Re: script edition nomemclature (10 replies, posted in Scripts)
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 re
import csv
from datetime import datetime
try:
from tksheet import Sheet
except ImportError:
messagebox.showerror("Erreur", "La librairie 'tksheet' est requise.\n\nExécutez cette commande :\npython -m pip install tksheet")
sys.exit(1)
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 = ('UUID', '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="Annuler (Undo)", command=lambda: self.sheet.undo()).pack(side=tk.LEFT, padx=5)
ttk.Button(top_frame, text="Rétablir (Redo)", command=lambda: self.sheet.redo()).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)
# TKSHEET
self.sheet = Sheet(tree_frame,
headers=list(self.columns_order),
empty_horizontal=0,
empty_vertical=0)
self.sheet.enable_bindings("all") # Active copier/coller, édition, tri, etc.
# Interdire l'ajout/suppression de lignes/colonnes via le clic droit pour éviter les erreurs
self.sheet.disable_bindings("rc_insert_column", "rc_delete_column", "rc_insert_row", "rc_delete_row")
# Rendre les colonnes LABEL (index 1) et FOLIO (index 16) en lecture seule
self.sheet.readonly_columns(columns=[1, 16])
self.sheet.pack(fill="both", expand=True)
# 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"""
# Réinitialiser l'état d'affichage pour que la vue par défaut (masquée) s'applique
self.columns_hidden = False
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_sheet()
self.info_label.config(text=f"Éléments affichés: {len(self.elements_data)}")
# Appeler la fonction pour masquer les colonnes optionnelles par défaut
self.toggle_columns()
def populate_sheet(self):
"""Remplir le tableau avec les données actuelles"""
sheet_data = []
for item in self.elements_data:
row = [
item.get('uuid', ''),
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', '')
]
sheet_data.append(row)
self.sheet.set_sheet_data(sheet_data)
# Masquer la colonne UUID (index 0)
self.sheet.hide_columns([0])
self.auto_resize_columns()
def auto_resize_columns(self):
"""Redimensionner les colonnes en fonction du contenu"""
sheet_data = self.sheet.get_sheet_data()
# Si pas de données, on arrête là pour éviter les erreurs d'index
if not sheet_data:
return
try:
# Largeur en caractères pour chaque colonne (minimum: largeur de l'en-tête)
col_char_widths = {i: len(str(h)) for i, h in enumerate(self.columns_order)}
# Calculer la largeur maximale en caractères nécessaire pour chaque colonne
for row in sheet_data:
for i, cell in enumerate(row):
col_char_widths[i] = max(col_char_widths.get(i, 0), len(str(cell)))
# Index des colonnes spéciales
desc_idx = self.columns_order.index('DESCRIPTION')
comment_idx = self.columns_order.index('COMMENTAIRE')
# Appliquer les largeurs calculées en pixels
for i, width_in_chars in col_char_widths.items():
pixel_width = width_in_chars * 7 + 20 # Approximation: 7px par caractère + 20px de marge
if i in [desc_idx, comment_idx]:
final_width = min(pixel_width, 500) # Cap à 500px pour les colonnes longues
else:
final_width = min(pixel_width, 300) # Cap à 300px pour les autres
self.sheet.column_width(column=i, width=max(final_width, 40)) # Largeur min de 40px
except (ValueError, AttributeError, IndexError):
# En cas d'erreur (colonne non trouvée ou méthode tksheet inexistante), on ne fait rien
pass
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.)
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 update_xml_element(self, element, field_name, value):
"""Mettre à jour les informations de l'élément dans le XML"""
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_text'):
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:
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"""
sheet_data = self.sheet.get_sheet_data()
if not sheet_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:
# Exclure la colonne UUID (index 0) pour l'export
columns = self.columns_order[1:]
with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile, delimiter=';')
writer.writerow(columns)
for row in sheet_data:
# Ignorer la première colonne (UUID)
writer.writerow(row[1:])
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é"""
selected_rows = self.sheet.get_selected_rows(return_tuple=True)
if not selected_rows:
messagebox.showwarning("Attention", "Sélectionnez des lignes")
return
if messagebox.askyesno("Confirmation", "Êtes-vous sûr de vouloir supprimer les éléments sélectionnés ?"):
# Récupérer les UUIDs avant suppression
sheet_data = self.sheet.get_sheet_data()
uuids_to_delete = [sheet_data[r][0] for r in selected_rows]
# 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') in uuids_to_delete:
diagram.remove(element)
# Retirer de la liste
self.elements_data = [e for e in self.elements_data if e['uuid'] not in uuids_to_delete]
# Supprimer du tableau
self.sheet.delete_rows(list(selected_rows))
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:
# Récupérer toutes les données du tableau
sheet_data = self.sheet.get_sheet_data()
# Créer un mapping UUID -> Element XML pour accès rapide
uuid_map = {}
root = self.tree_file.getroot()
for diagram in root.findall('.//diagram'):
for element in diagram.findall('.//element'):
uuid = element.get('uuid')
if uuid:
uuid_map[uuid] = element
# Mapping index colonne -> nom champ XML
# 0: UUID, 1: LABEL, 2: FABRICANT, etc.
col_map = {
1: 'label', 2: 'manufacturer', 3: 'designation', 4: 'description',
5: 'comment', 6: 'location', 7: 'plant', 8: 'auxiliary1',
9: 'auxiliary2', 10: 'function', 11: 'manufacturer_reference',
12: 'supplier', 13: 'machine_manufacturer_reference',
14: 'unity', 15: 'quantity'
}
for row in sheet_data:
uuid = row[0]
if uuid in uuid_map:
element = uuid_map[uuid]
for col_idx, field_name in col_map.items():
if col_idx < len(row):
val = str(row[col_idx]) if row[col_idx] is not None else ""
self.update_xml_element(element, field_name, val)
# 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."""
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']
indices_to_toggle = {self.columns_order.index(c) for c in columns_to_toggle if c in self.columns_order}
# Obtenir la liste de toutes les colonnes visibles possibles (tout sauf UUID à l'index 0)
all_possible_indices = set(range(1, len(self.columns_order)))
if self.columns_hidden:
# L'état est "caché", donc on veut tout afficher.
self.sheet.displayed_columns = sorted(list(all_possible_indices))
self.columns_hidden = False
else:
# L'état est "affiché", donc on veut cacher les colonnes optionnelles.
new_displayed = all_possible_indices - indices_to_toggle
self.sheet.displayed_columns = sorted(list(new_displayed))
self.columns_hidden = True
self.sheet.redraw()
self.auto_resize_columns()
if __name__ == "__main__":
root = tk.Tk()
app = QETAnalyzer(root)
root.mainloop()
36 2026-02-07 14:05:35
Re: script auto numérotation des fils par folio (6 replies, posted in Scripts)
import xml.etree.ElementTree as ET
import tkinter as tk
from tkinter import filedialog, messagebox
import os
class QETRenumberingApp:
def __init__(self, root):
self.root = root
self.root.title("Numérotation de Fils QElectroTech")
self.root.geometry("700x550")
# Variables de données
self.file_path = None
self.tree = None
self.root_xml = None
self.folios = [] # Liste des éléments <diagram>
# --- Interface Graphique ---
# Section Fichier
frame_file = tk.LabelFrame(root, text="Fichier Projet")
frame_file.pack(fill=tk.X, padx=10, pady=5)
self.btn_open = tk.Button(frame_file, text="Ouvrir fichier .qet", command=self.open_file)
self.btn_open.pack(side=tk.LEFT, padx=5, pady=5)
self.lbl_file = tk.Label(frame_file, text="Aucun fichier sélectionné", fg="gray")
self.lbl_file.pack(side=tk.LEFT, padx=5)
# Section Liste des Folios
frame_list = tk.LabelFrame(root, text="Sélection du Folio")
frame_list.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.listbox_folios = tk.Listbox(frame_list, selectmode=tk.SINGLE, font=("Consolas", 10))
self.listbox_folios.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
scrollbar = tk.Scrollbar(frame_list, orient="vertical", command=self.listbox_folios.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.listbox_folios.config(yscrollcommand=scrollbar.set)
self.listbox_folios.bind('<<ListboxSelect>>', self.on_folio_select)
# Section Options
frame_options = tk.LabelFrame(root, text="Paramètres de Numérotation")
frame_options.pack(fill=tk.X, padx=10, pady=5)
tk.Label(frame_options, text="Préfixe :").grid(row=0, column=0, padx=5, pady=5, sticky="e")
self.entry_prefix = tk.Entry(frame_options, width=10)
self.entry_prefix.grid(row=0, column=1, padx=5, pady=5, sticky="w")
self.entry_prefix.insert(0, "") # Vide par défaut
tk.Label(frame_options, text="Format :").grid(row=0, column=2, padx=15, pady=5, sticky="e")
self.var_format = tk.StringVar(value="0")
tk.Radiobutton(frame_options, text="0, 1, 2...", variable=self.var_format, value="0").grid(row=0, column=3)
tk.Radiobutton(frame_options, text="00, 01, 02...", variable=self.var_format, value="00").grid(row=0, column=4)
tk.Label(frame_options, text="Début :").grid(row=1, column=0, padx=5, pady=5, sticky="e")
self.entry_start = tk.Entry(frame_options, width=10)
self.entry_start.grid(row=1, column=1, padx=5, pady=5, sticky="w")
self.entry_start.insert(0, "0")
self.btn_set_08 = tk.Button(frame_options, text="Mettre 08", command=lambda: self.set_start_value("08"))
self.btn_set_08.grid(row=1, column=2, padx=5, pady=5, sticky="w")
# Section Actions
frame_actions = tk.Frame(root)
frame_actions.pack(fill=tk.X, padx=10, pady=10)
self.btn_process = tk.Button(frame_actions, text="Numéroter le folio sélectionné", command=self.process_folio, state=tk.DISABLED, bg="#dddddd", height=2)
self.btn_process.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
self.btn_save = tk.Button(frame_actions, text="Sauvegarder le projet sous...", command=self.save_file, state=tk.DISABLED, height=2)
self.btn_save.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=5)
def set_start_value(self, val):
self.entry_start.delete(0, tk.END)
self.entry_start.insert(0, val)
def open_file(self):
path = filedialog.askopenfilename(filetypes=[("Projet QElectroTech", "*.qet"), ("Fichiers XML", "*.xml")])
if path:
self.file_path = path
self.lbl_file.config(text=os.path.basename(path), fg="black")
self.parse_qet()
def parse_qet(self):
try:
self.tree = ET.parse(self.file_path)
self.root_xml = self.tree.getroot()
self.folios = []
self.listbox_folios.delete(0, tk.END)
# Recherche des diagrammes (folios)
for i, diagram in enumerate(self.root_xml.findall('diagram')):
title = diagram.get('title', 'Sans titre')
# Essayer de trouver le numéro de folio, sinon utiliser l'index
num = diagram.get('folio', str(i + 1))
label = f"N° {num} : {title}"
self.folios.append(diagram)
self.listbox_folios.insert(tk.END, label)
self.btn_save.config(state=tk.NORMAL)
self.btn_process.config(state=tk.DISABLED)
except Exception as e:
messagebox.showerror("Erreur", f"Impossible de lire le fichier :\n{e}")
def on_folio_select(self, event):
if self.listbox_folios.curselection():
self.btn_process.config(state=tk.NORMAL, bg="#aaffaa") # Vert clair quand actif
# Réinitialiser le début selon le format
default_val = "00" if self.var_format.get() == "00" else "0"
self.entry_start.delete(0, tk.END)
self.entry_start.insert(0, default_val)
else:
self.btn_process.config(state=tk.DISABLED, bg="#dddddd")
def process_folio(self):
idx = self.listbox_folios.curselection()
if not idx:
return
diagram = self.folios[idx[0]]
prefix = self.entry_prefix.get()
fmt = self.var_format.get()
try:
start_num = int(self.entry_start.get())
except ValueError:
start_num = 0
try:
count = self.renumber_wires_logic(diagram, prefix, fmt, start_num)
messagebox.showinfo("Succès", f"Folio renuméroté avec succès.\n{count} équipotentielles traitées.")
except Exception as e:
messagebox.showerror("Erreur", f"Une erreur est survenue lors de la numérotation :\n{e}")
def get_definitions(self, root_xml):
definitions = {}
collection = root_xml.find('collection')
if collection is None:
return definitions
def traverse(element, current_path):
for child in element:
if child.tag == 'category':
name = child.get('name')
new_path = f"{current_path}/{name}" if current_path else name
traverse(child, new_path)
elif child.tag == 'element':
name = child.get('name')
uri = f"embed://{current_path}/{name}" if current_path else f"embed://{name}"
definition = child.find('definition')
if definition is not None:
definitions[uri] = definition
traverse(collection, "")
return definitions
def renumber_wires_logic(self, diagram, prefix, fmt, start_num=0):
# 0. Index definitions
definitions = self.get_definitions(self.root_xml)
# 1. Identification des bornes de continuité et positions des terminaux
continuity_uuids = set()
terminals_pos = {} # (element_uuid, terminal_uuid) -> (x, y)
elements_pos = {} # uuid -> (x, y) pour position par défaut
for elem in diagram.findall('elements/element'):
uuid = elem.get('uuid')
type_uri = elem.get('type', '')
# Stockage position pour tri géométrique si le fil n'a pas de points
try:
x = float(elem.get('x', 0))
y = float(elem.get('y', 0))
elements_pos[uuid] = (x, y)
except:
continue
# Détection borne continuité
if 'continuite' in type_uri.lower():
continuity_uuids.add(uuid)
# Parse terminals from definition
def_node = definitions.get(type_uri)
if def_node is not None:
# Check link_type in definition if needed
if 'continuite' in def_node.get('link_type', '').lower():
continuity_uuids.add(uuid)
for term in def_node.findall('description/terminal'):
t_uuid = term.get('uuid')
if t_uuid:
try:
t_x = float(term.get('x', 0))
t_y = float(term.get('y', 0))
terminals_pos[(uuid, t_uuid)] = (x + t_x, y + t_y)
except:
pass
# 2. Construction du graphe de connexions (Union-Find)
conductors = diagram.findall('conductors/conductor')
parent = list(range(len(conductors)))
def find(i):
if parent[i] == i: return i
parent[i] = find(parent[i])
return parent[i]
def union(i, j):
root_i = find(i)
root_j = find(j)
if root_i != root_j:
parent[root_i] = root_j
# Dictionnaire : (ElementUUID, TerminalUUID) -> Liste d'index de conducteurs
terminals_map = {}
for i, cond in enumerate(conductors):
# Récupération des extrémités du fil
e1_uid = cond.get('element1')
t1_uid = cond.get('terminal1')
e2_uid = cond.get('element2')
t2_uid = cond.get('terminal2')
# Enregistrement des connexions
for e_uid, t_uid in [(e1_uid, t1_uid), (e2_uid, t2_uid)]:
if not e_uid: continue
# Si c'est une borne de continuité, on ignore le terminal spécifique
# pour considérer que tout ce qui touche cet élément est connecté.
if e_uid in continuity_uuids:
key = (e_uid, 'COMMON_POTENTIAL')
else:
key = (e_uid, t_uid)
if key not in terminals_map:
terminals_map[key] = []
terminals_map[key].append(i)
# Fusion des groupes (Union)
for key, indices in terminals_map.items():
base = indices[0]
for other in indices[1:]:
union(base, other)
# 3. Regroupement par équipotentielle
groups = {}
for i, cond in enumerate(conductors):
root = find(i)
if root not in groups:
groups[root] = []
groups[root].append(cond)
# 4. Filtrage et Tri
equipotentials_to_process = []
for root, group in groups.items():
# Vérifier si l'équipotentielle est déjà numérotée (verrouillée)
# On ignore si un des fils a un numéro qui n'est pas "" ou "_"
is_locked = False
for cond in group:
num = cond.get('num', '')
if num and num != "_":
is_locked = True
break
if is_locked:
continue
# Calcul de la position (le point le plus en haut à gauche de tout le réseau)
min_x = float('inf')
min_y = float('inf')
for cond in group:
# Vérifier les points du tracé du fil
points = cond.findall('point')
if points:
for pt in points:
px = float(pt.get('x', 0))
py = float(pt.get('y', 0))
if px < min_x: min_x = px
if py < min_y: min_y = py
else:
# Si pas de points (connexion directe), utiliser la position des terminaux
e1 = cond.get('element1')
t1 = cond.get('terminal1')
if e1 and t1 and (e1, t1) in terminals_pos:
tx, ty = terminals_pos[(e1, t1)]
if tx < min_x: min_x = tx
if ty < min_y: min_y = ty
elif e1 and e1 in elements_pos:
ex, ey = elements_pos[e1]
if ex < min_x: min_x = ex
if ey < min_y: min_y = ey
e2 = cond.get('element2')
t2 = cond.get('terminal2')
if e2 and t2 and (e2, t2) in terminals_pos:
tx, ty = terminals_pos[(e2, t2)]
if tx < min_x: min_x = tx
if ty < min_y: min_y = ty
elif e2 and e2 in elements_pos:
ex, ey = elements_pos[e2]
if ex < min_x: min_x = ex
if ey < min_y: min_y = ey
# Sécurité si infini
if min_x == float('inf'): min_x = 0
if min_y == float('inf'): min_y = 0
equipotentials_to_process.append({
'wires': group,
'x': min_x,
'y': min_y
})
# Tri : Haut en Bas (y), puis Gauche à Droite (x) pour un balayage par ligne.
equipotentials_to_process.sort(key=lambda e: (e['y'], e['x']))
# 5. Application de la numérotation
counter = start_num
for eq in equipotentials_to_process:
# Formatage du numéro
num_part = str(counter)
if fmt == "00" and counter < 10:
num_part = "0" + num_part
label = f"{prefix}{num_part}"
# Mise à jour de tous les fils du groupe
for cond in eq['wires']:
cond.set('num', label)
# Note: QET met à jour l'affichage automatiquement basé sur l'attribut 'num'
counter += 1
return len(equipotentials_to_process)
def save_file(self):
if not self.tree: return
path = filedialog.asksaveasfilename(defaultextension=".qet", filetypes=[("Projet QElectroTech", "*.qet")])
if path:
try:
self.tree.write(path, encoding="UTF-8", xml_declaration=True)
messagebox.showinfo("Sauvegardé", f"Fichier sauvegardé :\n{path}")
except Exception as e:
messagebox.showerror("Erreur", f"Erreur lors de la sauvegarde :\n{e}")
if __name__ == "__main__":
root = tk.Tk()
app = QETRenumberingApp(root)
root.mainloop()
37 2026-02-07 14:00:38
Topic: script auto numérotation des fils par folio (6 replies, posted in Scripts)
Voici un script que j'ai créer pour numéroter les fils par folio avec préfixe qui exclu les fils déjà numérotés, pratique pour les io.
la numérotation s'effectue de bas en haut gauche droite en sélectionnant folio par folio.
ne prend en charge qui si les borne ont un nom en francais "continuité"
38 2026-02-07 13:53:34
Topic: script edition nomemclature (10 replies, posted in Scripts)
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...
39 2019-04-17 21:24:54
Re: Fonctionnalité QElectroTech 0,8 (22 replies, posted in FR : Aide, suggestions, discussions, ...)
Bonjour tous
On pourrais ajouter un bouton rapide pour remplacer en un clic la formule d'un conducteur resultant de l'autonumerotation par son résultat. ( j'ai de temps en temps des numéros de fil qui tiennent compte des folios alors c'est un peu embétant avec les renvois)
40 2018-04-20 11:19:32
Re: Nouveautés de la version de développement 0.7 (554 replies, posted in News)
oui le type d'autonumerotation m+chifre est selectionné ainsi que celui des fils lors du multipaste.
(bug identique sous windows 7)
41 2018-04-19 23:41:49
Re: Nouveautés de la version de développement 0.7 (554 replies, posted in News)
sur mac je pose un contacteurs puis un moteur
les trois phase se numerote sauf la troisieme
tu pose le moteur puis le contacteurs, les trois phases se numerote correctement
multipaste sur les moteurs, M1 M1 M1 M1 M1 avec autonumerotation des elements cocher
multipaste sur les contacteur avec autoconnexion, les fils s'incrementent correctement
il y a un bug sur l'autonumerotation des fils dans les deux cas entre l'objet inferieur et l'objet superieur
qui se replique lors du multipaste....
42 2018-04-17 17:50:41
Re: Nouveautés de la version de développement 0.7 (554 replies, posted in News)
@scorpio
oui a la maison, mais j'ai des sites web en cours plus le temps
donc du coup je suis sur windows 7 au bureau.
Pour l'instant pas de bug sur les textes dynamiques, cela fonctionne carrément bien.
je ferais un test sur le mac des que j'ai 5 minutes
43 2018-04-17 16:57:14
Re: Nouveautés de la version de développement 0.7 (554 replies, posted in News)
Peut être un rappel d'une configuration de texte sur plusieurs symboles simultanément via un clic droit
pour les schémas existant
ça serait super cool comme fonction
44 2018-04-17 16:46:41
Re: Nouveautés de la version de développement 0.7 (554 replies, posted in News)
@JOSHUA
Effectivement, ça du être l'usine à gaz...
45 2018-04-16 23:45:06
Re: Nouveautés de la version de développement 0.7 (554 replies, posted in News)
@joshua
peut être que l'on pourrait la garder cette case a coché et faire en sorte que le commentaire s’intègre systématiquement dans la liste de texte dynamique si celui ci n'y est pas déja
ça doit faire un peu de taf a codé........mais cela permettrait de récupérer les projets qui ont été fait avec label + commentaire.
je pense que je dois pas être le seul dans ce cas la
j'ai essayé en éditant le projet en xml mais c'est galère....
46 2018-04-12 23:20:48
Re: Nouveautés de la version de développement 0.7 (554 replies, posted in News)
Salut tous
j'ai repris la dernière version et j'avais des commentaires qui étaient coché en affichage sur mes symboles dans le folio.
mais malheureusement lors de la conversion en texte dynamique, je suis obligé de les rajouter dans les texte dynamique à la mano sur chaque symbole.
Aurais je loupé un épisode ou si il y a une manip simple pour rajouté le champ commentaire sur une série de symboles déja dessiné ????
Yé suis preneur
47 2017-11-16 18:43:41
Re: Nouveautés de la version de développement 0.7 (554 replies, posted in News)
d
48 2017-11-16 18:41:50
Re: Nouveautés de la version de développement 0.7 (554 replies, posted in News)
b
49 2017-11-16 18:39:44
Re: Nouveautés de la version de développement 0.7 (554 replies, posted in News)
a
50 2017-11-16 18:38:21
Re: Nouveautés de la version de développement 0.7 (554 replies, posted in News)
...euh... J'ai commencer à faire la liste de toutes les propriétés en m'inspirant du logiciel commercial allemand qu'on m'oblige à utiliser, puis je me suis dit : "mais attends, avant de passer 1 heure là-dessus, est-ce vraiment nécessaire ?"
Pour vous donner une idée, ces propriétés sont, en vrac :
- données fabricant (nom du fabricant, référence, description, etc.)
- données fournisseur (nom du fournisseur, numéro de cammande, etc.)
- données géométriques (hauteur, largeur, profondeur, diamètre)
- données commerciales (prix, monnaie, rabait, etc.)
- données pour la maintenance (pièce de rechange, article en fin de vie, etc.)
- ...
C'est un sacrée catalogue ! J'ai pas compté, mais c'est une bonne centaine de propriétés pour 1 seul article![]()
Certes, ces infos peuvent être intéressantes, mais personne n'a le temps de renseigner tous ces machins. J'ai cottoyé assez de bureaux d'étude pour savoir que la plupart des champs restent vides...A tout bien y réfléchir, je pense que les données fournisseur (nom et numéro de commande) n'ont pas leur place dans QET car ce sont des données personnelles. Si on veut facilement partager nos collections, il faut exclure les données personnelles.
De toute facon, galexis et javdenech gèrent leurs achats avec des fichiers externes, si j'ai bien compris. C'est sûrement la bonne méthode. Comme ca les prix et autres données commerciales (délais de livraison, etc.) peuvent être actualisées par une personne autre que le concepteur des plans électriques (ce qui est très souvent le cas).
Perso, moi je suis satisfait avec les champs d'infos déjà disponibles dans QET. Si on en rajoute, on risque de transformer QET en usine à gaz...
@ galexis et javdenech :
pour votre "numéro de commande" fournisseur, vous devriez plutôt utliser le champ "numéro interne" car c'est le numéro que vous utilisez "en interne" pour faire le lien avec vos fichiers externe (xls, ods ou autre).
Enfin... vous bossez comme vous voulez, bien sûr, je voudrais juste qu'on se mette d'accord sur la structure des données pour faciliter plus tard les échanges.
nuri
tu as bien raison finalement ca sert a rien
Suffit que phoenix te change le short item code et c'est mort.
je ne les utilise jamais, ingérable
