macOS/tools: suite SEE/EPLAN (netlist, bornier+E/S, num. auto fils, BOM articles)
- qet_netlist.py : liste des connexions (de/vers, couleur, section) MD/CSV - qet_terminals_io.py : plan de bornier + liste E/S API - qet_wire_number.py : numerotation auto des conducteurs (.qet, format preserve + .bak) - qet_parts.py : BOM chiffree par jointure catalogue articles (CSV)
This commit is contained in:
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""qet_netlist — liste des connexions (netlist) depuis un .qet.
|
||||
Pour chaque conducteur : folio, num, de (élément/borne) -> vers (élément/borne), couleur, section.
|
||||
Usage: python3 qet_netlist.py projet.qet [--csv sortie.csv]"""
|
||||
import sys, os, csv, argparse
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
def bn(t): return os.path.splitext(os.path.basename((t or '').replace('\\','/')))[0]
|
||||
|
||||
def main():
|
||||
ap=argparse.ArgumentParser(); ap.add_argument("qet"); ap.add_argument("--csv")
|
||||
a=ap.parse_args(); root=ET.parse(a.qet).getroot()
|
||||
rows=[]
|
||||
for i,d in enumerate(root.findall("diagram"),1):
|
||||
# index élément par uuid -> repère/type
|
||||
elem={}
|
||||
for e in d.findall(".//element"):
|
||||
lab=""
|
||||
for ei in e.findall(".//elementInformation"):
|
||||
if ei.attrib.get("name")=="label" and (ei.text or "").strip(): lab=ei.text.strip()
|
||||
elem[e.attrib.get("uuid")]= lab or bn(e.attrib.get("type"))
|
||||
for c in d.findall(".//conductor"):
|
||||
ca=c.attrib
|
||||
rows.append({"folio":i,"num":ca.get("num",""),
|
||||
"borne1":ca.get("terminal1",""),"borne2":ca.get("terminal2",""),
|
||||
"couleur":ca.get("conductor_color",""),"section":ca.get("conductor_section",""),
|
||||
"fonction":ca.get("function",""),"câble":ca.get("cable",""),"type":ca.get("type","")})
|
||||
cols=["folio","num","borne1","borne2","couleur","section","fonction","câble","type"]
|
||||
print(f"# Netlist — {os.path.basename(a.qet)} ({len(rows)} connexions)\n")
|
||||
print("| "+" | ".join(cols)+" |"); print("| "+" | ".join("---" for _ in cols)+" |")
|
||||
for r in rows: print("| "+" | ".join(str(r.get(k,"")) for k in cols)+" |")
|
||||
if a.csv:
|
||||
with open(a.csv,"w",newline="",encoding="utf-8") as f:
|
||||
w=csv.DictWriter(f,fieldnames=cols); w.writeheader(); w.writerows(rows)
|
||||
print(f"\nCSV: {a.csv}", file=sys.stderr)
|
||||
if __name__=="__main__": main()
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""qet_parts — BOM chiffrée par jointure .qet × catalogue d'articles (style EPLAN parts).
|
||||
Lecture seule : associe chaque appareil (par repère ou type) à une ligne catalogue CSV
|
||||
(colonnes: cle, designation, fabricant, reference, prix). Sort une BOM chiffrée MD/CSV.
|
||||
Usage : python3 qet_parts.py projet.qet --catalog articles.csv [--by label|type] [--csv out.csv]"""
|
||||
import os, csv, argparse, collections
|
||||
import xml.etree.ElementTree as ET
|
||||
def bn(t): return os.path.splitext(os.path.basename((t or '').replace('\\','/')))[0]
|
||||
def lab(e):
|
||||
for ei in e.findall(".//elementInformation"):
|
||||
if ei.attrib.get("name")=="label" and (ei.text or "").strip(): return ei.text.strip()
|
||||
return ""
|
||||
def main():
|
||||
ap=argparse.ArgumentParser(); ap.add_argument("qet"); ap.add_argument("--catalog",required=True)
|
||||
ap.add_argument("--by",choices=["label","type"],default="type"); ap.add_argument("--csv")
|
||||
a=ap.parse_args()
|
||||
cat={}
|
||||
with open(a.catalog,encoding="utf-8") as f:
|
||||
for row in csv.DictReader(f): cat[(row.get("cle") or "").strip()]=row
|
||||
root=ET.parse(a.qet).getroot()
|
||||
IGNORE=("folio","renvoi","nomenclatur","spec_cablage","cartouche")
|
||||
keycount=collections.Counter()
|
||||
for e in root.findall(".//element"):
|
||||
t=bn(e.attrib.get("type"))
|
||||
if any(k in t.lower() for k in IGNORE): continue
|
||||
key = lab(e) if a.by=="label" else t
|
||||
if key: keycount[key]+=1
|
||||
rows=[]; total=0.0
|
||||
for key,qte in keycount.most_common():
|
||||
c=cat.get(key,{})
|
||||
prix=float(c.get("prix","0") or 0); sous=prix*qte; total+=sous
|
||||
rows.append({"clé":key,"qté":qte,"désignation":c.get("designation",""),
|
||||
"fabricant":c.get("fabricant",""),"référence":c.get("reference",""),
|
||||
"PU":f"{prix:.2f}","total":f"{sous:.2f}"})
|
||||
cols=["clé","qté","désignation","fabricant","référence","PU","total"]
|
||||
print(f"# BOM chiffrée — {os.path.basename(a.qet)} (jointure sur {a.by})\n")
|
||||
print("| "+" | ".join(cols)+" |"); print("| "+" | ".join("---" for _ in cols)+" |")
|
||||
for r in rows: print("| "+" | ".join(str(r[k]) for k in cols)+" |")
|
||||
print(f"\n**Total estimé : {total:.2f} €**")
|
||||
if a.csv:
|
||||
with open(a.csv,"w",newline="",encoding="utf-8") as f:
|
||||
w=csv.DictWriter(f,fieldnames=cols); w.writeheader(); w.writerows(rows)
|
||||
if __name__=="__main__": main()
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
"""qet_terminals_io — plan de bornier + liste d'E/S API depuis un .qet.
|
||||
Détecte les borniers (terminal_strip) et les éléments d'API (type contenant 'plc'/'automate'/'io').
|
||||
Usage: python3 qet_terminals_io.py projet.qet"""
|
||||
import os, argparse
|
||||
import xml.etree.ElementTree as ET
|
||||
def bn(t): return os.path.splitext(os.path.basename((t or '').replace('\\','/')))[0]
|
||||
def lab(e):
|
||||
for ei in e.findall(".//elementInformation"):
|
||||
if ei.attrib.get("name")=="label" and (ei.text or "").strip(): return ei.text.strip()
|
||||
return ""
|
||||
def main():
|
||||
ap=argparse.ArgumentParser(); ap.add_argument("qet"); a=ap.parse_args()
|
||||
root=ET.parse(a.qet).getroot()
|
||||
strips=root.findall(".//terminal_strip")+root.findall(".//terminalstrip")
|
||||
terms=[e for e in root.findall(".//element") if "born" in bn(e.attrib.get("type")).lower() or "terminal" in bn(e.attrib.get("type")).lower()]
|
||||
ios=[e for e in root.findall(".//element") if any(k in bn(e.attrib.get("type")).lower() for k in ("plc","automate","_io","entree","sortie","input","output"))]
|
||||
print(f"# Bornier & E/S API — {os.path.basename(a.qet)}\n")
|
||||
print(f"## Borniers\n\n- terminal_strip déclarés : {len(strips)}\n- éléments bornes détectés : {len(terms)}\n")
|
||||
if terms:
|
||||
print("| folio? | repère | type |"); print("| --- | --- | --- |")
|
||||
for e in terms[:200]: print(f"| - | {lab(e)} | {bn(e.attrib.get('type'))} |")
|
||||
print(f"\n## Entrées/Sorties API\n\n- éléments E/S détectés : {len(ios)}\n")
|
||||
if ios:
|
||||
print("| repère | type |"); print("| --- | --- |")
|
||||
for e in ios[:200]: print(f"| {lab(e)} | {bn(e.attrib.get('type'))} |")
|
||||
else:
|
||||
print("_(aucun élément API détecté dans ce projet)_")
|
||||
if __name__=="__main__": main()
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""qet_wire_number — numérotation automatique des conducteurs d'un .qet (style SEE/EPLAN).
|
||||
Attribue un numéro aux conducteurs sans numéro. Préserve le format (édition ciblée) + sauvegarde .bak.
|
||||
Schémas : --scheme global (1,2,3...) ou folio (F1-1, F1-2...). --force renumérote tout.
|
||||
Usage : python3 qet_wire_number.py projet.qet [--scheme folio|global] [--force] [--prefix N]"""
|
||||
import re, sys, os, argparse, shutil
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
def main():
|
||||
ap=argparse.ArgumentParser(); ap.add_argument("qet")
|
||||
ap.add_argument("--scheme",choices=["global","folio"],default="folio")
|
||||
ap.add_argument("--force",action="store_true"); ap.add_argument("--prefix",default="")
|
||||
a=ap.parse_args()
|
||||
txt=open(a.qet,encoding="utf-8").read()
|
||||
# bornes de folios (offsets des <diagram )
|
||||
dia_pos=[m.start() for m in re.finditer(r'<diagram\b', txt)]
|
||||
def folio_of(pos):
|
||||
f=0
|
||||
for p in dia_pos:
|
||||
if p<=pos: f+=1
|
||||
else: break
|
||||
return max(f,1)
|
||||
counters={}; gcount=0; changed=0
|
||||
def repl(m):
|
||||
nonlocal gcount,changed
|
||||
tag=m.group(0); pos=m.start()
|
||||
cur=re.search(r'\bnum="([^"]*)"',tag)
|
||||
has=cur and cur.group(1).strip()
|
||||
if has and not a.force: return tag
|
||||
f=folio_of(pos)
|
||||
if a.scheme=="folio":
|
||||
counters[f]=counters.get(f,0)+1; val=f"{a.prefix}F{f}-{counters[f]}"
|
||||
else:
|
||||
gcount+=1; val=f"{a.prefix}{gcount}"
|
||||
changed+=1
|
||||
if cur: return tag[:cur.start()]+f'num="{val}"'+tag[cur.end():]
|
||||
return tag[:-1]+f' num="{val}">'
|
||||
new=re.sub(r'<conductor\b[^>]*>', repl, txt)
|
||||
# validation : XML bien formé
|
||||
try: ET.fromstring(new)
|
||||
except ET.ParseError as e:
|
||||
print("ERREUR: XML invalide après édition, abandon:",e); sys.exit(1)
|
||||
shutil.copyfile(a.qet, a.qet+".bak")
|
||||
open(a.qet,"w",encoding="utf-8").write(new)
|
||||
print(f"OK : {changed} conducteurs numérotés (schéma {a.scheme}). Sauvegarde : {os.path.basename(a.qet)}.bak")
|
||||
if __name__=="__main__": main()
|
||||
Reference in New Issue
Block a user