# Assembleur libre : placement (x,y) + liens typés (puissance/can/rs485/eth) entre ports nommés. import xml.etree.ElementTree as ET, json, sys SVG="http://www.w3.org/2000/svg";INK="http://www.inkscape.org/namespaces/inkscape" ET.register_namespace("",SVG);ET.register_namespace("inkscape",INK) def lname(t):return t.split('}')[-1] STYLE={ "power":{"stroke":"#0d2b3b","stroke-width":"1.8"}, "can" :{"stroke":"#31a3dd","stroke-width":"1.4","stroke-dasharray":"3 2"}, "rs485":{"stroke":"#e69500","stroke-width":"1.4","stroke-dasharray":"3 2"}, "eth" :{"stroke":"#00b4d8","stroke-width":"1.4","stroke-dasharray":"3 2"}, "relais":{"stroke":"#caa000","stroke-width":"1.3","stroke-dasharray":"1 1.5"}, "pwr" :{"stroke":"#e6194b","stroke-width":"1.6"}, "pe" :{"stroke":"#00ff00","stroke-width":"1.6"}, } LBL={"can":"CAN","rs485":"RS485","eth":"ETH"} def ports(root): d={} for el in root.iter(): l=el.get(f"{{{INK}}}label") if l and l.startswith("term:") and lname(el.tag)=="circle": d[l[5:]]=(float(el.get("cx")),float(el.get("cy"))) return d def inject(root,disp,rep): for el in root.iter(): l=el.get(f"{{{INK}}}label") if not l or not l.startswith("lbl:"):continue k=l[4:] if k=="repere" and rep is not None:el.text=rep elif k in disp:el.text=disp[k] def vb(root):v=[float(x) for x in root.get("viewBox").split()];return v[2],v[3] scene=json.load(open(sys.argv[1])) W=scene.get("w",520);H=scene.get("h",420) master=ET.Element(f"{{{SVG}}}svg",{"viewBox":f"0 0 {W} {H}"}) ET.SubElement(master,f"{{{SVG}}}rect",{"x":"0","y":"0","width":str(W),"height":str(H),"fill":"#fff"}) links_g=ET.SubElement(master,f"{{{SVG}}}g") P={}; SIDE={} for c in scene["blocks"]: r=ET.parse(c["block"]).getroot();w,h=vb(r) ox,oy=c["at"] inject(r,c.get("display",{}),c.get("repere")) r.set("x",str(ox));r.set("y",str(oy));r.set("width",str(w));r.set("height",str(h));r.set("overflow","visible") master.append(r) vbw,vbh=vb(r) for k,(px,py) in ports(r).items(): P[f'{c["id"]}.{k}']=(ox+px,oy+py) if px<=6: SIDE[f'{c["id"]}.{k}']="L" elif px>=vbw-6: SIDE[f'{c["id"]}.{k}']="R" elif py<=6: SIDE[f'{c["id"]}.{k}']="T" else: SIDE[f'{c["id"]}.{k}']="B" def elbow(p1,p2,horizfirst): x1,y1=p1;x2,y2=p2 if abs(x1-x2)<0.6 or abs(y1-y2)<0.6: return [p1,p2] return [p1,(x2,y1),p2] if horizfirst else [p1,(x1,y2),p2] for w in scene["links"]: typ=w.get("type","power") a=P[w["from"]] if w["to"].endswith(".RAIL"): # cible = barrette rail bid=w["to"][:-5] L=P[f"{bid}.RAIL_L"]; R=P[f"{bid}.RAIL_R"] ry=L[1]; ex=min(max(a[0],L[0]),R[0]) # tombe à l'aplomb, borné à la barre b=(ex,ry); pts=[a,(a[0],ry)] if abs(a[0]-ex)<0.6 else [a,(a[0],ry),(ex,ry)] # pastille de jonction sur la barre import xml.etree.ElementTree as _ET _ET.SubElement(links_g,f"{{{SVG}}}circle",{"cx":f"{ex:.1f}","cy":f"{ry:.1f}","r":"2.4","fill":"#00ff00"}) elif w["to"]=="GND": # terre locale : symbole au port, écarté du corps import xml.etree.ElementTree as _ET sx,sy=a; side=SIDE.get(w["from"],"B"); LEAD=10 g=_ET.SubElement(links_g,f"{{{SVG}}}g") def L(x1,y1,x2,y2,wd): _ET.SubElement(g,f"{{{SVG}}}line",{"x1":f"{x1:.1f}","y1":f"{y1:.1f}","x2":f"{x2:.1f}","y2":f"{y2:.1f}","stroke":"#00ff00","stroke-width":wd}) if side in ("L","R"): d=-1 if side=="L" else 1 bx=sx+d*LEAD # patte horizontale L(sx,sy,bx,sy,"1.6") # ⏚ vertical au bout (3 traits décroissants, axe vertical) L(bx,sy-6,bx,sy+6,"2"); L(bx+d*3,sy-4,bx+d*3,sy+4,"2"); L(bx+d*6,sy-2,bx+d*6,sy+2,"2") else: by=sy+LEAD L(sx,sy,sx,by,"1.6") L(sx-6,by,sx+6,by,"2"); L(sx-4,by+3,sx+4,by+3,"2"); L(sx-2,by+6,sx+2,by+6,"2") continue else: b=P[w["to"]]; hf=w.get("horizfirst",True); pts=elbow(a,b,hf) d="M "+" L ".join(f"{x:.1f} {y:.1f}" for x,y in pts) attrs={"d":d,"fill":"none"};attrs.update(STYLE[typ]) ET.SubElement(links_g,f"{{{SVG}}}path",attrs) if typ in LBL: mx,my=pts[len(pts)//2] t=ET.SubElement(links_g,f"{{{SVG}}}text",{"x":f"{mx+3:.1f}","y":f"{my-3:.1f}", "font-family":"'IBM Plex Mono',monospace","font-size":"7","fill":STYLE[typ]["stroke"]}) t.text=LBL[typ] ET.ElementTree(master).write(sys.argv[2],encoding="utf-8",xml_declaration=True) print("écrit",sys.argv[2])